Skip to content

Instantly share code, notes, and snippets.

@jermdavis
Created February 17, 2019 15:54
Show Gist options
  • Save jermdavis/0b27c613e7e252e4a1cd68fc530b7d77 to your computer and use it in GitHub Desktop.
Save jermdavis/0b27c613e7e252e4a1cd68fc530b7d77 to your computer and use it in GitHub Desktop.
An attempt at an error-friendly pipeline
public struct Either<SUCCESS, FAILURE>
{
private readonly bool _isSuccess;
private readonly SUCCESS _success;
private readonly FAILURE _failure;
public bool IsSuccess => _isSuccess;
public bool IsFailure => !IsSuccess;
public SUCCESS SuccessValue => _success;
public FAILURE FailureValue => _failure;
public Either(SUCCESS value)
{
_isSuccess = true;
_success = value;
_failure = default(FAILURE);
}
public Either(FAILURE value)
{
_isSuccess = false;
_success = default(SUCCESS);
_failure = value;
}
public override string ToString()
{
if(_isSuccess)
{
return _success.ToString();
}
else
{
return _failure.ToString();
}
}
public static implicit operator Either<SUCCESS, FAILURE>(FAILURE value) => new Either<SUCCESS, FAILURE>(value);
public static implicit operator Either<SUCCESS, FAILURE>(SUCCESS value) => new Either<SUCCESS, FAILURE>(value);
public static implicit operator SUCCESS(Either<SUCCESS, FAILURE> value) => value._success;
public static implicit operator FAILURE(Either<SUCCESS, FAILURE> value) => value._failure;
public T Match<T>(Func<SUCCESS, T> successFn, Func<FAILURE, T> failureFn)
{
if(_isSuccess)
{
return successFn(_success);
}
else
{
return failureFn(_failure);
}
}
}
public interface IErrorAwarePipelineStep<INPUT, OUTPUT, ERROR>
{
Either<OUTPUT, ERROR> Process(Either<INPUT, ERROR> input);
}
public interface IErrorAwarePipeline<INPUT, OUTPUT, ERROR> : IErrorAwarePipelineStep<INPUT, OUTPUT, ERROR>
{
Func<Either<INPUT, ERROR>, Either<OUTPUT, ERROR>> PipelineSteps { get; }
}
public static class ErrorAwarePipelineStepExtensions
{
public static Either<OUTPUT, ERROR> ErrorAwareStep<INPUT, OUTPUT, ERROR>(this Either<INPUT, ERROR> input, IErrorAwarePipelineStep<INPUT, OUTPUT, ERROR> step)
{
return step.Process(input);
}
}
public abstract class ErrorAwarePipelineStep<INPUT, OUTPUT, ERROR> : IErrorAwarePipelineStep<INPUT, OUTPUT, ERROR>
{
public abstract Either<OUTPUT, ERROR> ProcessSuccessInput(Either<INPUT, ERROR> input);
public virtual Either<OUTPUT, ERROR> ProcessErrorInput(Either<INPUT, ERROR> input)
{
return input.FailureValue;
}
public Either<OUTPUT, ERROR> Process(Either<INPUT, ERROR> input)
{
if (input.IsSuccess)
{
return ProcessSuccessInput(input);
}
else
{
return ProcessErrorInput(input);
}
}
}
public abstract class ErrorAwarePipeline<INPUT, OUTPUT, ERROR> : IErrorAwarePipeline<INPUT, OUTPUT, ERROR>
{
public Func<Either<INPUT, ERROR>, Either<OUTPUT, ERROR>> PipelineSteps { get; protected set; }
public Either<OUTPUT, ERROR> Process(Either<INPUT, ERROR> input)
{
return PipelineSteps(input);
}
}
public class ErrorAwareStringToIntStep : ErrorAwarePipelineStep<string, int, Exception>
{
public override Either<int, Exception> ProcessSuccessInput(Either<string, Exception> input)
{
int value;
if (int.TryParse(input.SuccessValue, out value))
{
return value;
}
else
{
return new Exception("Can't parse string to integer");
}
}
}
public class ErrorAwareMultiplyStep : ErrorAwarePipelineStep<int, float, Exception>
{
public override Either<float, Exception> ProcessSuccessInput(Either<int, Exception> input)
{
return input * 2.1f;
}
}
public class ErrorStateChangeStep : ErrorAwarePipelineStep<float, float, Exception>
{
public override Either<float, Exception> ProcessSuccessInput(Either<float, Exception> input)
{
return input;
}
public override Either<float, Exception> ProcessErrorInput(Either<float, Exception> input)
{
return new Exception("This is a custom exception", input.FailureValue);
}
}
public class ExampleErrorAwarePipeline : ErrorAwarePipeline<string, float, Exception>
{
public ExampleErrorAwarePipeline()
{
PipelineSteps = input => input
.ErrorAwareStep(new ErrorAwareStringToIntStep())
.ErrorAwareStep(new ErrorAwareMultiplyStep())
.ErrorAwareStep(new ErrorStateChangeStep());
}
}
public class Program
{
private static IErrorAwarePipeline<string, float, Exception> _pipeline = new ExampleErrorAwarePipeline();
private static void runPipeline(string input)
{
Console.WriteLine("Running pipeline:");
var result = _pipeline
.Process(input)
.Match(
f => $"Success: Transformed {input}[{input.GetType().Name}] to {f}[{f.GetType().Name}]",
f => $"Error: Transformed {input}[{input.GetType().Name}] to {f}[{f.GetType().Name}]"
);
Console.WriteLine(result);
Console.WriteLine();
}
private static void Main(string[] args)
{
runPipeline("27");
runPipeline("0");
runPipeline("0.1");
runPipeline("hello");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment