Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/DotNext.Tests/OptionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,12 @@ public static void ConvertNoneToRefType()
}

[Fact]
public static void ConvertToOptional()
public static unsafe void ConvertToOptional()
{
Equal(Optional.None<double>(), Optional.None<int>().Convert(ToDouble));
Equal(Optional.None<double>(), Optional.None<int>().Convert<double>(&ToDouble));
Equal(42D, new Optional<int>(42).Convert(ToDouble));
Equal(42D, new Optional<int>(42).Convert<double>(&ToDouble));

static Optional<double> ToDouble(int value) => double.CreateChecked(value);
}
Expand Down
78 changes: 78 additions & 0 deletions src/DotNext.Tests/ResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,84 @@ public static unsafe void Conversion2()
Equal(EnvironmentVariableTarget.Machine, result.Convert(&Convert.ToInt32).Error);
}

[Fact]
public static unsafe void ConvertToResult()
{
// Standard conversion
Result<string> validStringResult = "20";
var convertedResult1 = validStringResult.Convert(ToInt);
True(convertedResult1.IsSuccessful);
Equal(20, convertedResult1);

// Unsafe standard conversion
var convertedResult2 = validStringResult.Convert<int>(&ToInt);
True(convertedResult2.IsSuccessful);
Equal(20, convertedResult2);

// Failing conversion
Result<string> invalidStringResult = "20F";
var convertedResult3 = invalidStringResult.Convert(ToInt);
False(convertedResult3.IsSuccessful);
IsType<FormatException>(convertedResult3.Error);

// Unsafe failing conversion
var convertedResult4 = invalidStringResult.Convert<int>(&ToInt);
False(convertedResult4.IsSuccessful);
IsType<FormatException>(convertedResult4.Error);

// Conversion of unsuccessful Result<T>
Result<string> exceptionResult = new(new ArgumentNullException());
var convertedResult5 = exceptionResult.Convert(ToInt);
False(convertedResult5.IsSuccessful);
IsType<ArgumentNullException>(convertedResult5.Error);

// Unsafe conversion of unsuccessful Result<T>
var convertedResult6 = exceptionResult.Convert<int>(&ToInt);
False(convertedResult6.IsSuccessful);
IsType<ArgumentNullException>(convertedResult6.Error);

static Result<int> ToInt(string value) => int.TryParse(value, out var result) ? result : throw new FormatException();
}

[Fact]
public unsafe static void ConvertToResultWithErrorCode()
{
// Standard conversion
Result<string, EnvironmentVariableTarget> validStringResult = "20";
var convertedResult1 = validStringResult.Convert(ToInt);
True(convertedResult1.IsSuccessful);
Equal(20, convertedResult1);

// Unsafe standard conversion
var convertedResult2 = validStringResult.Convert<int>(&ToInt);
True(convertedResult2.IsSuccessful);
Equal(20, convertedResult2);

// Failing conversion
Result<string, EnvironmentVariableTarget> invalidStringResult = "20F";
var convertedResult3 = invalidStringResult.Convert(ToInt);
False(convertedResult3.IsSuccessful);
Equal(EnvironmentVariableTarget.Machine, convertedResult3.Error);

// Unsafe failing conversion
var convertedResult4 = invalidStringResult.Convert<int>(&ToInt);
False(convertedResult4.IsSuccessful);
Equal(EnvironmentVariableTarget.Machine, convertedResult4.Error);

// Conversion of unsuccessful Result<T>
Result<string, EnvironmentVariableTarget> errorCodeResult = new(EnvironmentVariableTarget.User);
var convertedResult5 = errorCodeResult.Convert(ToInt);
False(convertedResult5.IsSuccessful);
Equal(EnvironmentVariableTarget.User, convertedResult5.Error);

// Unsafe conversion of unsuccessful Result<T>
var convertedResult6 = errorCodeResult.Convert<int>(&ToInt);
False(convertedResult6.IsSuccessful);
Equal(EnvironmentVariableTarget.User, convertedResult6.Error);

static Result<int, EnvironmentVariableTarget> ToInt(string value) => int.TryParse(value, out var result) ? new(result) : new(EnvironmentVariableTarget.Machine);
}

[Fact]
public static void HandleException()
{
Expand Down
71 changes: 71 additions & 0 deletions src/DotNext/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,30 @@ private Result<TResult> Convert<TResult, TConverter>(TConverter converter)
return result;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Result<TResult> ConvertResult<TResult, TConverter>(TConverter converter)
where TConverter : struct, ISupplier<T, Result<TResult>>
{
Result<TResult> result;
if (exception is null)
{
try
{
result = converter.Invoke(value);
}
catch (Exception e)
{
result = new(e);
}
}
else
{
result = new(exception);
}

return result;
}

/// <summary>
/// If the successful result is present, apply the provided mapping function hiding any exception
/// caused by the converter.
Expand All @@ -241,6 +265,16 @@ private Result<TResult> Convert<TResult, TConverter>(TConverter converter)
public Result<TResult> Convert<TResult>(Converter<T, TResult> converter)
=> Convert<TResult, DelegatingConverter<T, TResult>>(converter);

/// <summary>
/// If successful result is present, apply the provided mapping function. If not,
/// forward the exception.
/// </summary>
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
/// <returns>The conversion result.</returns>
public Result<TResult> Convert<TResult>(Converter<T, Result<TResult>> converter)
=> ConvertResult<TResult, DelegatingConverter<T, Result<TResult>>>(converter);

/// <summary>
/// If the successful result is present, apply the provided mapping function hiding any exception
/// caused by the converter.
Expand All @@ -252,6 +286,17 @@ public Result<TResult> Convert<TResult>(Converter<T, TResult> converter)
public unsafe Result<TResult> Convert<TResult>(delegate*<T, TResult> converter)
=> Convert<TResult, Supplier<T, TResult>>(converter);

/// <summary>
/// If successful result is present, apply the provided mapping function. If not,
/// forward the exception.
/// </summary>
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
/// <returns>The conversion result.</returns>
[CLSCompliant(false)]
public unsafe Result<TResult> Convert<TResult>(delegate*<T, Result<TResult>> converter)
=> ConvertResult<TResult, Supplier<T, Result<TResult>>>(converter);

/// <summary>
/// Attempts to extract value from the container if it is present.
/// </summary>
Expand Down Expand Up @@ -563,6 +608,11 @@ private Result<TResult, TError> Convert<TResult, TConverter>(TConverter converte
where TConverter : struct, ISupplier<T, TResult>
=> IsSuccessful ? new(converter.Invoke(value)) : new(Error);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Result<TResult, TError> ConvertResult<TResult, TConverter>(TConverter converter)
where TConverter : struct, ISupplier<T, Result<TResult, TError>>
=> IsSuccessful ? converter.Invoke(value) : new(Error);

/// <summary>
/// If the successful result is present, apply the provided mapping function hiding any exception
/// caused by the converter.
Expand All @@ -573,6 +623,16 @@ private Result<TResult, TError> Convert<TResult, TConverter>(TConverter converte
public Result<TResult, TError> Convert<TResult>(Converter<T, TResult> converter)
=> Convert<TResult, DelegatingConverter<T, TResult>>(converter);

/// <summary>
/// If successful result is present, apply the provided mapping function. If not,
/// forward the error.
/// </summary>
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
/// <returns>The conversion result.</returns>
public Result<TResult, TError> Convert<TResult>(Converter<T, Result<TResult, TError>> converter)
=> ConvertResult<TResult, DelegatingConverter<T, Result<TResult, TError>>>(converter);

/// <summary>
/// If the successful result is present, apply the provided mapping function hiding any exception
/// caused by the converter.
Expand All @@ -584,6 +644,17 @@ public Result<TResult, TError> Convert<TResult>(Converter<T, TResult> converter)
public unsafe Result<TResult, TError> Convert<TResult>(delegate*<T, TResult> converter)
=> Convert<TResult, Supplier<T, TResult>>(converter);

/// <summary>
/// If successful result is present, apply the provided mapping function. If not,
/// forward the error.
/// </summary>
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
/// <returns>The conversion result.</returns>
[CLSCompliant(false)]
public unsafe Result<TResult, TError> Convert<TResult>(delegate*<T, Result<TResult, TError>> converter)
=> ConvertResult<TResult, Supplier<T, Result<TResult, TError>>>(converter);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T OrInvoke<TSupplier>(TSupplier defaultFunc)
where TSupplier : struct, ISupplier<T>
Expand Down