Skip to content

Commit c60b940

Browse files
authored
Merge pull request #258 from julianthurner/master
Added convenience methods for easier Monad creation and Monad chaining
2 parents 53e9c0d + 8164aa7 commit c60b940

File tree

3 files changed

+152
-1
lines changed

3 files changed

+152
-1
lines changed

src/DotNext.Tests/OptionalTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,12 @@ public static void ConvertNoneToRefType()
322322
}
323323

324324
[Fact]
325-
public static void ConvertToOptional()
325+
public static unsafe void ConvertToOptional()
326326
{
327327
Equal(Optional.None<double>(), Optional.None<int>().Convert(ToDouble));
328+
Equal(Optional.None<double>(), Optional.None<int>().Convert<double>(&ToDouble));
328329
Equal(42D, new Optional<int>(42).Convert(ToDouble));
330+
Equal(42D, new Optional<int>(42).Convert<double>(&ToDouble));
329331

330332
static Optional<double> ToDouble(int value) => double.CreateChecked(value);
331333
}

src/DotNext.Tests/ResultTests.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,84 @@ public static unsafe void Conversion2()
185185
Equal(EnvironmentVariableTarget.Machine, result.Convert(&Convert.ToInt32).Error);
186186
}
187187

188+
[Fact]
189+
public static unsafe void ConvertToResult()
190+
{
191+
// Standard conversion
192+
Result<string> validStringResult = "20";
193+
var convertedResult1 = validStringResult.Convert(ToInt);
194+
True(convertedResult1.IsSuccessful);
195+
Equal(20, convertedResult1);
196+
197+
// Unsafe standard conversion
198+
var convertedResult2 = validStringResult.Convert<int>(&ToInt);
199+
True(convertedResult2.IsSuccessful);
200+
Equal(20, convertedResult2);
201+
202+
// Failing conversion
203+
Result<string> invalidStringResult = "20F";
204+
var convertedResult3 = invalidStringResult.Convert(ToInt);
205+
False(convertedResult3.IsSuccessful);
206+
IsType<FormatException>(convertedResult3.Error);
207+
208+
// Unsafe failing conversion
209+
var convertedResult4 = invalidStringResult.Convert<int>(&ToInt);
210+
False(convertedResult4.IsSuccessful);
211+
IsType<FormatException>(convertedResult4.Error);
212+
213+
// Conversion of unsuccessful Result<T>
214+
Result<string> exceptionResult = new(new ArgumentNullException());
215+
var convertedResult5 = exceptionResult.Convert(ToInt);
216+
False(convertedResult5.IsSuccessful);
217+
IsType<ArgumentNullException>(convertedResult5.Error);
218+
219+
// Unsafe conversion of unsuccessful Result<T>
220+
var convertedResult6 = exceptionResult.Convert<int>(&ToInt);
221+
False(convertedResult6.IsSuccessful);
222+
IsType<ArgumentNullException>(convertedResult6.Error);
223+
224+
static Result<int> ToInt(string value) => int.TryParse(value, out var result) ? result : throw new FormatException();
225+
}
226+
227+
[Fact]
228+
public unsafe static void ConvertToResultWithErrorCode()
229+
{
230+
// Standard conversion
231+
Result<string, EnvironmentVariableTarget> validStringResult = "20";
232+
var convertedResult1 = validStringResult.Convert(ToInt);
233+
True(convertedResult1.IsSuccessful);
234+
Equal(20, convertedResult1);
235+
236+
// Unsafe standard conversion
237+
var convertedResult2 = validStringResult.Convert<int>(&ToInt);
238+
True(convertedResult2.IsSuccessful);
239+
Equal(20, convertedResult2);
240+
241+
// Failing conversion
242+
Result<string, EnvironmentVariableTarget> invalidStringResult = "20F";
243+
var convertedResult3 = invalidStringResult.Convert(ToInt);
244+
False(convertedResult3.IsSuccessful);
245+
Equal(EnvironmentVariableTarget.Machine, convertedResult3.Error);
246+
247+
// Unsafe failing conversion
248+
var convertedResult4 = invalidStringResult.Convert<int>(&ToInt);
249+
False(convertedResult4.IsSuccessful);
250+
Equal(EnvironmentVariableTarget.Machine, convertedResult4.Error);
251+
252+
// Conversion of unsuccessful Result<T>
253+
Result<string, EnvironmentVariableTarget> errorCodeResult = new(EnvironmentVariableTarget.User);
254+
var convertedResult5 = errorCodeResult.Convert(ToInt);
255+
False(convertedResult5.IsSuccessful);
256+
Equal(EnvironmentVariableTarget.User, convertedResult5.Error);
257+
258+
// Unsafe conversion of unsuccessful Result<T>
259+
var convertedResult6 = errorCodeResult.Convert<int>(&ToInt);
260+
False(convertedResult6.IsSuccessful);
261+
Equal(EnvironmentVariableTarget.User, convertedResult6.Error);
262+
263+
static Result<int, EnvironmentVariableTarget> ToInt(string value) => int.TryParse(value, out var result) ? new(result) : new(EnvironmentVariableTarget.Machine);
264+
}
265+
188266
[Fact]
189267
public static void HandleException()
190268
{

src/DotNext/Result.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,30 @@ private Result<TResult> Convert<TResult, TConverter>(TConverter converter)
231231
return result;
232232
}
233233

234+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
235+
private Result<TResult> ConvertResult<TResult, TConverter>(TConverter converter)
236+
where TConverter : struct, ISupplier<T, Result<TResult>>
237+
{
238+
Result<TResult> result;
239+
if (exception is null)
240+
{
241+
try
242+
{
243+
result = converter.Invoke(value);
244+
}
245+
catch (Exception e)
246+
{
247+
result = new(e);
248+
}
249+
}
250+
else
251+
{
252+
result = new(exception);
253+
}
254+
255+
return result;
256+
}
257+
234258
/// <summary>
235259
/// If the successful result is present, apply the provided mapping function hiding any exception
236260
/// caused by the converter.
@@ -241,6 +265,16 @@ private Result<TResult> Convert<TResult, TConverter>(TConverter converter)
241265
public Result<TResult> Convert<TResult>(Converter<T, TResult> converter)
242266
=> Convert<TResult, DelegatingConverter<T, TResult>>(converter);
243267

268+
/// <summary>
269+
/// If successful result is present, apply the provided mapping function. If not,
270+
/// forward the exception.
271+
/// </summary>
272+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
273+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
274+
/// <returns>The conversion result.</returns>
275+
public Result<TResult> Convert<TResult>(Converter<T, Result<TResult>> converter)
276+
=> ConvertResult<TResult, DelegatingConverter<T, Result<TResult>>>(converter);
277+
244278
/// <summary>
245279
/// If the successful result is present, apply the provided mapping function hiding any exception
246280
/// caused by the converter.
@@ -252,6 +286,17 @@ public Result<TResult> Convert<TResult>(Converter<T, TResult> converter)
252286
public unsafe Result<TResult> Convert<TResult>(delegate*<T, TResult> converter)
253287
=> Convert<TResult, Supplier<T, TResult>>(converter);
254288

289+
/// <summary>
290+
/// If successful result is present, apply the provided mapping function. If not,
291+
/// forward the exception.
292+
/// </summary>
293+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
294+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
295+
/// <returns>The conversion result.</returns>
296+
[CLSCompliant(false)]
297+
public unsafe Result<TResult> Convert<TResult>(delegate*<T, Result<TResult>> converter)
298+
=> ConvertResult<TResult, Supplier<T, Result<TResult>>>(converter);
299+
255300
/// <summary>
256301
/// Attempts to extract value from the container if it is present.
257302
/// </summary>
@@ -563,6 +608,11 @@ private Result<TResult, TError> Convert<TResult, TConverter>(TConverter converte
563608
where TConverter : struct, ISupplier<T, TResult>
564609
=> IsSuccessful ? new(converter.Invoke(value)) : new(Error);
565610

611+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
612+
private Result<TResult, TError> ConvertResult<TResult, TConverter>(TConverter converter)
613+
where TConverter : struct, ISupplier<T, Result<TResult, TError>>
614+
=> IsSuccessful ? converter.Invoke(value) : new(Error);
615+
566616
/// <summary>
567617
/// If the successful result is present, apply the provided mapping function hiding any exception
568618
/// caused by the converter.
@@ -573,6 +623,16 @@ private Result<TResult, TError> Convert<TResult, TConverter>(TConverter converte
573623
public Result<TResult, TError> Convert<TResult>(Converter<T, TResult> converter)
574624
=> Convert<TResult, DelegatingConverter<T, TResult>>(converter);
575625

626+
/// <summary>
627+
/// If successful result is present, apply the provided mapping function. If not,
628+
/// forward the error.
629+
/// </summary>
630+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
631+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
632+
/// <returns>The conversion result.</returns>
633+
public Result<TResult, TError> Convert<TResult>(Converter<T, Result<TResult, TError>> converter)
634+
=> ConvertResult<TResult, DelegatingConverter<T, Result<TResult, TError>>>(converter);
635+
576636
/// <summary>
577637
/// If the successful result is present, apply the provided mapping function hiding any exception
578638
/// caused by the converter.
@@ -584,6 +644,17 @@ public Result<TResult, TError> Convert<TResult>(Converter<T, TResult> converter)
584644
public unsafe Result<TResult, TError> Convert<TResult>(delegate*<T, TResult> converter)
585645
=> Convert<TResult, Supplier<T, TResult>>(converter);
586646

647+
/// <summary>
648+
/// If successful result is present, apply the provided mapping function. If not,
649+
/// forward the error.
650+
/// </summary>
651+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
652+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
653+
/// <returns>The conversion result.</returns>
654+
[CLSCompliant(false)]
655+
public unsafe Result<TResult, TError> Convert<TResult>(delegate*<T, Result<TResult, TError>> converter)
656+
=> ConvertResult<TResult, Supplier<T, Result<TResult, TError>>>(converter);
657+
587658
[MethodImpl(MethodImplOptions.AggressiveInlining)]
588659
private T OrInvoke<TSupplier>(TSupplier defaultFunc)
589660
where TSupplier : struct, ISupplier<T>

0 commit comments

Comments
 (0)