Skip to content

Commit 2ebaed8

Browse files
christophercalmChristopher Calmes
andauthored
Add Bind in addition to Map to support Railway Oriented Programming (#215)
* removed some overloads * Added configure await --------- Co-authored-by: Christopher Calmes <[email protected]>
1 parent 7d71398 commit 2ebaed8

File tree

6 files changed

+1882
-34
lines changed

6 files changed

+1882
-34
lines changed
Lines changed: 324 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,338 @@
11
using System;
22
using System.Linq;
3+
using System.Threading.Tasks;
34

45
namespace Ardalis.Result
56
{
67
public static class ResultExtensions
78
{
89
/// <summary>
9-
/// Transforms a Result's type from a source type to a destination type. If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
10+
/// Transforms a Result's type from a source type to a destination type.
11+
/// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
1012
/// </summary>
11-
/// <typeparam name="TSource"></typeparam>
12-
/// <typeparam name="TDestination"></typeparam>
13-
/// <param name="result"></param>
14-
/// <param name="func"></param>
15-
/// <returns></returns>
16-
/// <exception cref="NotSupportedException"></exception>
13+
/// <typeparam name="TSource">The type of the value contained in the source Result.</typeparam>
14+
/// <typeparam name="TDestination">The type of the value to be returned in the destination Result.</typeparam>
15+
/// <param name="result">The source Result to transform.</param>
16+
/// <param name="func">A function to transform the source value to the destination type.</param>
17+
/// <returns>A Result containing the transformed value or the appropriate error status.</returns>
18+
/// <exception cref="NotSupportedException">Thrown when the Result status is not supported.</exception>
1719
public static Result<TDestination> Map<TSource, TDestination>(this Result<TSource> result, Func<TSource, TDestination> func)
1820
{
19-
switch (result.Status)
20-
{
21-
case ResultStatus.Ok: return func(result);
22-
case ResultStatus.Created: return string.IsNullOrEmpty(result.Location)
23-
? Result<TDestination>.Created(func(result.Value))
24-
: Result<TDestination>.Created(func(result.Value), result.Location);
25-
case ResultStatus.NotFound: return result.Errors.Any()
26-
? Result<TDestination>.NotFound(result.Errors.ToArray())
27-
: Result<TDestination>.NotFound();
28-
case ResultStatus.Unauthorized: return result.Errors.Any()
29-
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
30-
: Result<TDestination>.Unauthorized();
31-
case ResultStatus.Forbidden: return result.Errors.Any()
32-
? Result<TDestination>.Forbidden(result.Errors.ToArray())
33-
: Result<TDestination>.Forbidden();
34-
case ResultStatus.Invalid: return Result<TDestination>.Invalid(result.ValidationErrors);
35-
case ResultStatus.Error: return Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId));
36-
case ResultStatus.Conflict: return result.Errors.Any()
37-
? Result<TDestination>.Conflict(result.Errors.ToArray())
38-
: Result<TDestination>.Conflict();
39-
case ResultStatus.CriticalError: return Result<TDestination>.CriticalError(result.Errors.ToArray());
40-
case ResultStatus.Unavailable: return Result<TDestination>.Unavailable(result.Errors.ToArray());
41-
case ResultStatus.NoContent: return Result<TDestination>.NoContent();
42-
default:
43-
throw new NotSupportedException($"Result {result.Status} conversion is not supported.");
44-
}
21+
return result.Status switch
22+
{
23+
ResultStatus.Ok => (Result<TDestination>)func(result),
24+
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
25+
? Result<TDestination>.Created(func(result.Value))
26+
: Result<TDestination>.Created(func(result.Value), result.Location),
27+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
28+
};
29+
}
30+
31+
public static Result<TDestination> Map<TDestination>(this Result result, Func<TDestination> func)
32+
{
33+
return result.Status switch
34+
{
35+
ResultStatus.Ok => Result<TDestination>.Success(func()),
36+
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
37+
? Result<TDestination>.Created(func())
38+
: Result<TDestination>.Created(func(), result.Location),
39+
_ => HandleNonSuccessStatus<TDestination>(result),
40+
};
41+
}
42+
43+
public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
44+
this Result<TSource> result,
45+
Func<TSource, Task<TDestination>> func)
46+
{
47+
return result.Status switch
48+
{
49+
ResultStatus.Ok => Result<TDestination>.Success(await func(result.Value)),
50+
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
51+
? Result<TDestination>.Created(await func(result.Value))
52+
: Result<TDestination>.Created(await func(result.Value), result.Location),
53+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
54+
};
55+
}
56+
57+
58+
public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
59+
this Task<Result<TSource>> resultTask,
60+
Func<TSource, Task<TDestination>> func)
61+
{
62+
var result = await resultTask;
63+
return result.Status switch
64+
{
65+
ResultStatus.Ok => Result<TDestination>.Success(await func(result.Value)),
66+
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
67+
? Result<TDestination>.Created(await func(result.Value))
68+
: Result<TDestination>.Created(await func(result.Value), result.Location),
69+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
70+
};
71+
}
72+
73+
public static async Task<Result<TDestination>> MapAsync<TDestination>(
74+
this Result result,
75+
Func<Task<TDestination>> func)
76+
{
77+
return result.Status switch
78+
{
79+
ResultStatus.Ok => Result<TDestination>.Success(await func()),
80+
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
81+
? Result<TDestination>.Created(await func())
82+
: Result<TDestination>.Created(await func(), result.Location),
83+
_ => HandleNonSuccessStatus<TDestination>(result),
84+
};
85+
}
86+
87+
public static async Task<Result<TDestination>> MapAsync<TDestination>(
88+
this Task<Result> resultTask,
89+
Func<Task<TDestination>> func)
90+
{
91+
var result = await resultTask;
92+
return await result.MapAsync(func);
93+
}
94+
95+
96+
public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
97+
this Task<Result<TSource>> resultTask,
98+
Func<TSource, TDestination> func)
99+
{
100+
var result = await resultTask;
101+
return result.Map(func);
102+
}
103+
104+
public static async Task<Result<TDestination>> MapAsync<TDestination>(
105+
this Task<Result> resultTask,
106+
Func<TDestination> func)
107+
{
108+
var result = await resultTask;
109+
return result.Map(func);
110+
}
111+
112+
/// <summary>
113+
/// Transforms a Result's type from a source type to a destination type.
114+
/// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
115+
/// </summary>
116+
/// <typeparam name="TSource">The type of the value contained in the source Result.</typeparam>
117+
/// <typeparam name="TDestination">The type of the value to be returned in the destination Result.</typeparam>
118+
/// <param name="result">The source Result to transform.</param>
119+
/// <param name="func">A function to transform the source value to the destination type.</param>
120+
/// <returns>A Result containing the transformed value or the appropriate error status.</returns>
121+
/// <exception cref="NotSupportedException">Thrown when the Result status is not supported.</exception>
122+
public static Result<TDestination> Bind<TSource, TDestination>(this Result<TSource> result, Func<TSource, Result<TDestination>> bindFunc)
123+
{
124+
return result.Status switch
125+
{
126+
ResultStatus.Ok => bindFunc(result.Value),
127+
ResultStatus.Created => bindFunc(result.Value),
128+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
129+
};
130+
}
131+
132+
public static Result<TDestination> Bind<TDestination>(this Result result, Func<Result, Result<TDestination>> bindFunc)
133+
{
134+
return result.Status switch
135+
{
136+
ResultStatus.Ok => bindFunc(result.Value),
137+
ResultStatus.Created => bindFunc(result.Value),
138+
_ => HandleNonSuccessStatus<TDestination>(result),
139+
};
140+
}
141+
142+
public static Result Bind<TSource>(this Result<TSource> result, Func<Result<TSource>, Result> bindFunc)
143+
{
144+
return result.Status switch
145+
{
146+
ResultStatus.Ok => bindFunc(result.Value),
147+
ResultStatus.Created => bindFunc(result.Value),
148+
_ => HandleNonSuccessStatus(result),
149+
};
150+
}
151+
152+
public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
153+
this Task<Result<TSource>> resultTask,
154+
Func<TSource, Task<Result<TDestination>>> bindFunc)
155+
{
156+
var result = await resultTask;
157+
return result.Status switch
158+
{
159+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
160+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
161+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
162+
};
163+
}
164+
165+
public static async Task<Result> BindAsync<TSource>(
166+
this Task<Result<TSource>> resultTask,
167+
Func<TSource, Task<Result>> bindFunc)
168+
{
169+
var result = await resultTask;
170+
return result.Status switch
171+
{
172+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
173+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
174+
_ => HandleNonSuccessStatus(result),
175+
};
176+
}
177+
178+
public static async Task<Result> BindAsync<TSource>(
179+
this Result<TSource> result,
180+
Func<TSource, Task<Result>> bindFunc)
181+
{
182+
return result.Status switch
183+
{
184+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
185+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
186+
_ => HandleNonSuccessStatus(result),
187+
};
188+
}
189+
190+
public static async Task<Result> BindAsync(
191+
this Result result,
192+
Func<Result, Task<Result>> bindFunc)
193+
{
194+
return result.Status switch
195+
{
196+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
197+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
198+
_ => HandleNonSuccessStatus(result),
199+
};
200+
}
201+
202+
public static async Task<Result<TDestination>> BindAsync<TDestination>(
203+
this Task<Result> resultTask,
204+
Func<Result, Task<Result<TDestination>>> bindFunc)
205+
{
206+
var result = await resultTask;
207+
return result.Status switch
208+
{
209+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
210+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
211+
_ => HandleNonSuccessStatus<TDestination>(result),
212+
};
213+
}
214+
215+
public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
216+
this Result<TSource> result,
217+
Func<TSource, Task<Result<TDestination>>> bindFunc)
218+
{
219+
return result.Status switch
220+
{
221+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
222+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
223+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
224+
};
225+
}
226+
227+
public static async Task<Result> BindAsync<TSource, TDestination>(
228+
this Result<TSource> result,
229+
Func<TSource, Task<Result>> bindFunc)
230+
{
231+
return result.Status switch
232+
{
233+
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
234+
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
235+
_ => HandleNonSuccessStatus(result),
236+
};
237+
}
238+
239+
public static async Task<Result> BindAsync(this Task<Result> resultTask, Func<Result, Task<Result>> bindFunc)
240+
{
241+
var result = await resultTask;
242+
return result.Status switch
243+
{
244+
ResultStatus.Ok => await bindFunc(result).ConfigureAwait(false),
245+
ResultStatus.Created => await bindFunc(result).ConfigureAwait(false),
246+
_ => HandleNonSuccessStatus(result),
247+
};
248+
}
249+
250+
public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
251+
this Task<Result<TSource>> resultTask,
252+
Func<TSource, Result<TDestination>> bindFunc)
253+
{
254+
var result = await resultTask;
255+
return result.Status switch
256+
{
257+
ResultStatus.Ok => bindFunc(result.Value),
258+
ResultStatus.Created => bindFunc(result.Value),
259+
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
260+
};
261+
}
262+
263+
private static Result<TDestination> HandleNonSuccessStatus<TSource, TDestination>(Result<TSource> result)
264+
{
265+
return result.Status switch
266+
{
267+
ResultStatus.NotFound => result.Errors.Any()
268+
? Result<TDestination>.NotFound(result.Errors.ToArray())
269+
: Result<TDestination>.NotFound(),
270+
ResultStatus.Unauthorized => result.Errors.Any()
271+
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
272+
: Result<TDestination>.Unauthorized(),
273+
ResultStatus.Forbidden => result.Errors.Any()
274+
? Result<TDestination>.Forbidden(result.Errors.ToArray())
275+
: Result<TDestination>.Forbidden(),
276+
ResultStatus.Invalid => Result<TDestination>.Invalid(result.ValidationErrors),
277+
ResultStatus.Error => Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
278+
ResultStatus.Conflict => result.Errors.Any()
279+
? Result<TDestination>.Conflict(result.Errors.ToArray())
280+
: Result<TDestination>.Conflict(),
281+
ResultStatus.CriticalError => Result<TDestination>.CriticalError(result.Errors.ToArray()),
282+
ResultStatus.Unavailable => Result<TDestination>.Unavailable(result.Errors.ToArray()),
283+
ResultStatus.NoContent => Result<TDestination>.NoContent(),
284+
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
285+
};
286+
}
287+
288+
private static Result<TDestination> HandleNonSuccessStatus<TDestination>(Result result)
289+
{
290+
return result.Status switch
291+
{
292+
ResultStatus.NotFound => result.Errors.Any()
293+
? Result<TDestination>.NotFound(result.Errors.ToArray())
294+
: Result<TDestination>.NotFound(),
295+
ResultStatus.Unauthorized => result.Errors.Any()
296+
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
297+
: Result<TDestination>.Unauthorized(),
298+
ResultStatus.Forbidden => result.Errors.Any()
299+
? Result<TDestination>.Forbidden(result.Errors.ToArray())
300+
: Result<TDestination>.Forbidden(),
301+
ResultStatus.Invalid => Result<TDestination>.Invalid(result.ValidationErrors),
302+
ResultStatus.Error => Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
303+
ResultStatus.Conflict => result.Errors.Any()
304+
? Result<TDestination>.Conflict(result.Errors.ToArray())
305+
: Result<TDestination>.Conflict(),
306+
ResultStatus.CriticalError => Result<TDestination>.CriticalError(result.Errors.ToArray()),
307+
ResultStatus.Unavailable => Result<TDestination>.Unavailable(result.Errors.ToArray()),
308+
ResultStatus.NoContent => Result<TDestination>.NoContent(),
309+
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
310+
};
311+
}
312+
313+
private static Result HandleNonSuccessStatus<TSource>(Result<TSource> result)
314+
{
315+
return result.Status switch
316+
{
317+
ResultStatus.NotFound => result.Errors.Any()
318+
? Result.NotFound(result.Errors.ToArray())
319+
: Result.NotFound(),
320+
ResultStatus.Unauthorized => result.Errors.Any()
321+
? Result.Unauthorized(result.Errors.ToArray())
322+
: Result.Unauthorized(),
323+
ResultStatus.Forbidden => result.Errors.Any()
324+
? Result.Forbidden(result.Errors.ToArray())
325+
: Result.Forbidden(),
326+
ResultStatus.Invalid => Result.Invalid(result.ValidationErrors),
327+
ResultStatus.Error => Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
328+
ResultStatus.Conflict => result.Errors.Any()
329+
? Result.Conflict(result.Errors.ToArray())
330+
: Result.Conflict(),
331+
ResultStatus.CriticalError => Result.CriticalError(result.Errors.ToArray()),
332+
ResultStatus.Unavailable => Result.Unavailable(result.Errors.ToArray()),
333+
ResultStatus.NoContent => Result.NoContent(),
334+
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
335+
};
45336
}
46337
}
47338
}

0 commit comments

Comments
 (0)