Created
February 13, 2023 14:38
-
-
Save wgross/1dd8148fb84e671f11b85bea506d2cac to your computer and use it in GitHub Desktop.
POC for a rust like return
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Fact] | |
void Return_result() | |
{ | |
Result Function() => Result.Ok(); | |
Assert.True(Function() is Ok); | |
} | |
[Fact] | |
void Return_result_value() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
Assert.Equal(10,Function().Value); | |
Assert.True(Function() is Result<int> { Value: 10 }); | |
Assert.True(Function() is Ok<int> { Value: 10 }); | |
} | |
[Fact] | |
void Return_result_has_value_on_success() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
Assert.True(Function().HasValue); | |
} | |
[Fact] | |
void Return_result_on_error() | |
{ | |
Result Function() => Result.Error("fail"); | |
Assert.True(Function() is Error { Reason.Message: "fail" }); | |
} | |
[Fact] | |
void Return_result_hasnt_value_on_error() | |
{ | |
Result<int> Function() => Result<int>.Error("fail"); | |
Assert.False(Function().HasValue); | |
Assert.True(Function() is Error<int> {Reason.Message:"fail"}); | |
} | |
[Fact] | |
void Throw_on_error_value_from_string() | |
{ | |
Result<int> Function() => Result<int>.Error("fail"); | |
var result = Assert.Throws<InvalidOperationException>(() => Function().Value); | |
Assert.Equal("There is no value available: see inner exeception", result.Message); | |
Assert.Equal("fail", result.InnerException.Message); | |
Assert.NotNull(result.InnerException.Data["CallerMemberNameAttribute"]); | |
Assert.NotNull(result.InnerException.Data["CallerLineNumberAttribute"]); | |
Assert.NotNull(result.InnerException.Data["StackTrace"]); | |
Assert.True(Function() is Error<int> { Reason: Exception {Message:"fail"} }); | |
} | |
[Fact] | |
void Throw_on_error_value_from_exception() | |
{ | |
Result<int> Function() => Result<int>.Error(new InvalidOperationException("fail")); | |
var result = Assert.Throws<InvalidOperationException>(() => Function().Value); | |
Assert.Equal("There is no value available: see inner exeception", result.Message); | |
Assert.Equal("fail", result.InnerException.Message); | |
Assert.NotNull(result.InnerException.Data["CallerMemberNameAttribute"]); | |
Assert.NotNull(result.InnerException.Data["CallerLineNumberAttribute"]); | |
Assert.NotNull(result.InnerException.Data["StackTrace"]); | |
Assert.True(Function() is Error<int> { Reason: InvalidOperationException {Message:"fail"} }); | |
} | |
[Fact] | |
void Check_for_error_from_string_1() | |
{ | |
Result Function() => Result.Error(new InvalidOperationException("fail")); | |
Assert.True(Function() is Error { Reason: InvalidOperationException {Message:"fail"} }); | |
} | |
[Fact] | |
void Check_for_error_from_string_2() | |
{ | |
Result<int> Function() => Result<int>.Error("fail"); | |
var result = Assert.Throws<InvalidOperationException>(() => Function().Value); | |
Assert.Equal("There is no value available: see inner exeception", result.Message); | |
Assert.Equal("fail", result.InnerException.Message); | |
Assert.NotNull(result.InnerException.Data["CallerMemberNameAttribute"]); | |
Assert.NotNull(result.InnerException.Data["CallerLineNumberAttribute"]); | |
Assert.NotNull(result.InnerException.Data["StackTrace"]); | |
} | |
[Fact] | |
void Check_for_error_from_exception() | |
{ | |
Result<int> Function() => Result<int>.Error(new InvalidOperationException("fail")); | |
var result = Assert.Throws<InvalidOperationException>(() => Function().Value); | |
Assert.Equal("There is no value available: see inner exeception", result.Message); | |
Assert.Equal("fail",result.InnerException.Message); | |
Assert.NotNull(result.InnerException.Data["CallerMemberNameAttribute"]); | |
Assert.NotNull(result.InnerException.Data["CallerLineNumberAttribute"]); | |
Assert.NotNull(result.InnerException.Data["StackTrace"]); | |
} | |
[Fact] | |
void Check_for_success() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
Assert.True(Function() is Ok<int>); | |
Assert.Equal(10, Function() is Ok<int> ok ? ok.Value : 0); | |
} | |
[Fact] | |
void Switch_on_success() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
switch (Function()) | |
{ | |
case Ok<int> ok: | |
Assert.True(true); | |
break; | |
default: | |
Assert.True(false); | |
break; | |
} | |
} | |
[Fact] | |
void Switch_expr_success() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
Assert.True(Function() switch | |
{ | |
Ok<int> ok => true, | |
_ => false | |
}); | |
} | |
[Fact] | |
void Match_success() | |
{ | |
Result<int> Function() => Result<int>.Ok(10); | |
Assert.True(Function().Match(onOk:_ => true, onError:(e => false))); | |
} | |
[Fact] | |
void Match_error() | |
{ | |
Result<int> Function() => Result<int>.Error("fail"); | |
Assert.False(Function().Match(onOk: _ => true, onError: e => | |
{ | |
Assert.Equal("fail", e.Message); | |
Assert.NotNull(e.Data["CallerMemberNameAttribute"]); | |
Assert.NotNull(e.Data["CallerLineNumberAttribute"]); | |
Assert.NotNull(e.Data["StackTrace"]); | |
return false; | |
})); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ExceptionExtensions | |
{ | |
public static Exception WithData(this Exception exception, string key, object value) | |
{ | |
exception.Data[key??"<null>"] = value; | |
return exception; | |
} | |
} | |
public interface Result | |
{ | |
public static Ok Ok() => new Ok(); | |
public static Error Error( | |
string reason, | |
[CallerMemberName] string callerMemberName = "", | |
[CallerLineNumber] int callerLineNumber = 0) | |
{ | |
return Error(new Exception(reason), callerMemberName, callerLineNumber); | |
} | |
public static Error Error( | |
Exception reason, | |
[CallerMemberName] string callerMemberName = "", | |
[CallerLineNumber] int callerLineNumber = 0) | |
{ | |
return new Error | |
{ | |
Reason = reason | |
.WithData(nameof(CallerMemberNameAttribute), callerMemberName) | |
.WithData(nameof(CallerLineNumberAttribute), callerLineNumber) | |
.WithData(nameof(Environment.StackTrace), EnhancedStackTrace.Current().ToString()) | |
}; | |
} | |
} | |
/// <summary> | |
/// Defines the interface of an result. Every result has a property <see cref="Value"/> which either returns | |
/// the result value or throws an error if invoked unchecked. | |
/// </summary> | |
public interface Result<T> : Result | |
{ | |
public static Ok<T> Ok(T value) => new Ok<T> { Value = value }; | |
public static Error<T> Error( | |
string reason, | |
[CallerMemberName] string callerMemberName = "", | |
[CallerLineNumber] int callerLineNumber = 0) | |
{ | |
return Error(new Exception(reason), callerMemberName, callerLineNumber); | |
} | |
public static Error<T> Error( | |
Exception reason, | |
[CallerMemberName] string callerMemberName = "", | |
[CallerLineNumber] int callerLineNumber = 0) | |
{ | |
return new Error<T> | |
{ | |
Reason = reason | |
.WithData(nameof(CallerMemberNameAttribute), callerMemberName) | |
.WithData(nameof(CallerLineNumberAttribute), callerLineNumber) | |
.WithData(nameof(Environment.StackTrace), EnhancedStackTrace.Current().ToString()) | |
}; | |
} | |
public T Value { get; } | |
/// <summary> | |
/// Use to inslect OK or error with conversion | |
/// </summary> | |
public bool HasValue => this is Ok<T>; | |
/// <summary> | |
/// Chain a processing action for success or failure | |
/// </summary> | |
public R Match<R>(Func<T,R> onOk,Func<Exception,R> onError) => this is Ok<T> ok | |
? onOk(ok.Value) | |
: onError(((Error<T>)this).Reason); | |
} | |
public static class ResultExtension | |
{ | |
public static R Match<R, T>(this Result<T> result, Func<T, R> onOk = null, Func<T, R> onError = null ) => result switch | |
{ | |
Ok<T> ok => onOk.Invoke(ok.Value), | |
_ => default | |
}; | |
} | |
public readonly struct Ok : Result | |
{ | |
} | |
public readonly struct Ok<T> : Result<T> | |
{ | |
public T Value { get; init; } | |
} | |
public readonly struct Error : Result | |
{ | |
public Exception Reason { get; init; } | |
} | |
public readonly struct Error<T> : Result<T> | |
{ | |
public Exception Reason { get; init; } | |
public T Value | |
{ | |
// the the reason as an inner exceptions. B yths you have the stack trace of the error an an inner exception | |
// and the stack trace where the error was 'discovered' | |
get => throw new InvalidOperationException("There is no value available: see inner exeception", this.Reason); | |
init => throw new NotImplementedException(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment