-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"Add utility method to combine multiple ErrorOr instances" #120
Comments
I fell like an extension method would help to communicate which value will be used if no ErrorOr-object has any errors: public static ErrorOr<TValue> AppendErrors<TValue>(this ErrorOr<TValue> errorOr, params ErrorOr<object>[] errors)
{
List<Error> combinedErrors = errorOr.ErrorsOrEmptyList;
foreach (ErrorOr<object> error in errors)
{
combinedErrors.AddRange(error.ErrorsOrEmptyList);
}
return combinedErrors.Count == 0 ? errorOr : combinedErrors;
} var success1 = new ErrorOr<int>(1);
var success2 = new ErrorOr<int>(2);
var failure = ErrorOr<int>.From(new List<Error> { Error.Validation("ValidationError", "Validation failed") });
var result1 = success1.AppendErrors(success2);
var result2 = success1.AppendErrors(success2, failure);
result1.ThenDo(x => Console.WriteLine(x)).ElseDo(x => { foreach (var error in x) Console.WriteLine(error.Description); }); // writes 1
result2.ThenDo(x => Console.WriteLine(x)).ElseDo(x => { foreach (var error in x) Console.WriteLine(error.Description); }); // writes Validation failed What do you think? If that looks good to you, I'll open a PR |
The idea is really good, and the changes you've made are clear. However, I noticed that you're using Here’s how you can modify the method to work with the correct type: public static ErrorOr<TValue> AppendErrors<TValue>(this ErrorOr<TValue> errorOr, params ErrorOr<TValue>[] errors)
{
List<Error> combinedErrors = errorOr.ErrorsOrEmptyList;
foreach (ErrorOr<TValue> error in errors)
{
combinedErrors.AddRange(error.ErrorsOrEmptyList);
}
return combinedErrors.Count == 0 ? errorOr : combinedErrors;
} If these changes look good to you, you can open a PR, and I'll review it. Thanks for your work! |
I agree that public ErrorOr<User> AuthorizeAsAdmin()
{
ErrorOr<User> user = GetUser();
ErrorOr<Success> hasPermission = CheckPermissions(user, "admin");
ErrorOr<int> calcResult = (1 + 1).ToErrorOr();
return user.AppendErrors(hasPermission, calcResult);
}
public static ErrorOr<TValue> AppendErrors<TValue>(this ErrorOr<TValue> errorOr, params IErrorOr[] errors)
{
...
} |
Thanks for the explanation! I see your point about wanting to combine different types of ErrorOr instances into one call. You're right that in many cases, the types of the instances being combined won't match, and using IErrorOr[] allows for more flexibility in handling this scenario. The idea of using IErrorOr[] makes sense because it provides a way to handle multiple different types without being restricted to a single type parameter. This would allow you to append errors across different types like ErrorOr, ErrorOr, and ErrorOr in the same operation. However, one consideration I have is that using IErrorOr[] would mean we lose the type safety that comes with generics, since we would be dealing with a non-generic interface. This could potentially introduce runtime issues if someone were to accidentally mix incompatible types, though I understand that this is less of an issue when you only care about the errors and not the values. I think this approach could be quite useful in many scenarios, but I'd also like to make sure that we maintain type safety where possible, especially if this becomes a commonly used extension method. Would you be open to perhaps adding an overload for the AppendErrors method that allows both ErrorOr[] and IErrorOr[]? This way, we can have both flexible multi-type support and still maintain the option for type-safe handling when needed. Thanks again for the suggestion! |
Sure, I can add both versions, great suggestion 👍 |
wip, this is only the basic implementation. ToDo: - AppendErrors with ErrorOrs of same type using generics - AppendErrors to `Task<ErrorOr>` - Tests :) Refs: amantinband#120
Combining multiple results would be very nice feature, but perhaps maybe there is more general approach possible. I am afraid that use cases presented above - where only first result type matters - might rarely be the case in real world. I can imagine a lot of use cases when you have multiple results with different types and want to combine them to give either comosite object or list of all errors encountered so far. Is it doable with See classic example above: record Person(string FirstName, string LastName, DateOnly DateOfBirth);
// Inputs from user - potentially invalid
ErrorOr<string> firstName = "John";
ErrorOr<string> lastName = "Doe";
ErrorOr<DateOnly> dateOfBirth = DateOnly.Parse("1990-03-19");
// Domain object - must be either valid or list of errors
ErrorOrFactory.Combine(firstName, lastName, dateOfBirth)
.Then(t =>
{
// Tuple deconstruction into lambda args currently is not seem to be supported at the moment.
var (firstName, lastName, dateOfBirth) = t;
return new Person(firstName, lastName, dateOfBirth);
})
.Switch(
person => WritePerson(Console.Out, person),
errors => WriteErrors(Console.Error, errors)); In order to achieve above effect one can add bunch of methods like this: public static ErrorOr<(T1, T2, T3)> Combine<T1, T2, T3>(ErrorOr<T1> v1, ErrorOr<T2> v2, ErrorOr<T3> v3)
{
IErrorOr[] args = [v1, v2, v3];
List<Error> errors = [];
foreach (var arg in args)
{
if (arg.IsError)
{
errors.AddRange(arg.Errors!);
}
}
return errors.Count > 0 ? errors : (v1.Value, v2.Value, v3.Value);
} Downside of this solution is that only limited amout of such methods can be added i.e. up to 8-tuples. @amantinband It would be nice to get some design guidelines and decisions in this issue before merging #125. Is there more elegant and general solution for this? |
I agree, that would be a nice feature. The eight tuple restriction should probably not be an issue in most scenarios - I can't think of a situation where I had to construct a result object from more than eight different sources without questionable design decisions. I think the previous proposal is still valid though. In most projects I've worked on, we had command handlers that call utility methods returning Some input from @amantinband would be great, yes. I don't think there is a rush to get this feature merged quickly. |
Am am facing a similar issue: public ErrorOr<int> ParseInt(string s) { ... }
var values = new[] {"1", "2", "x"};
var result = values.Select(ParseInt); The problem being the result is now I have this extension method: public static ErrorOr<List<T>> Collect<T>(this IEnumerable<ErrorOr<T>> errorOrs)
{
var errors = new List<Error>();
var values = new List<T>();
foreach (var errorOr in errorOrs)
{
if (errorOr.IsError)
{
errors.AddRange(errorOr.Errors);
}
else
{
values.Add(errorOr.Value);
}
}
return errors.Count > 0 ? errors : values;
} Which lets you do: var values = new[] {"1", "2", "x"};
var result = values.Select(ParseInt).Collect(); Which now gives This doesn't help the "combine multiple type into a single ErrorOr" problem (I think that tuples are the only way there), but does solve the original issue. |
Yes, now I see that those solutions are complementary rather than mutually exclusive. I like your version with |
That's so good |
Feature Request
Summary:
Add a utility method to combine multiple
ErrorOr
instances, collecting all errors into a singleErrorOr
if any operation fails.Details:
The text was updated successfully, but these errors were encountered: