Skip to content
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 AppendErrors method and update documentation/tests #125

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions ErrorOr.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=925b9865_002D8ec9_002D489c_002Da728_002D0918ac5c4418/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="AggregationExtensionsTests #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::0E84EC69-4E4C-4195-907D-06C96D0140A6::net8.0::Tests.ErrorOr.AggregationExtensionsTests&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=eac8010a_002Dec6e_002D4cc4_002D847a_002D7ec6651faf49/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="AppendErrors_ShouldCombineErrorsWithSuccess_WhenSuccessAndFailureAreCombined_WithUserType" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::0E84EC69-4E4C-4195-907D-06C96D0140A6::net8.0::Tests.ErrorOr.AggregationExtensionsTests.AppendErrors_WithMixedSuccessAndErrorInstances_AppendsOnlyErrors&lt;/TestId&gt;&#xD;
&lt;TestId&gt;xUnit::0E84EC69-4E4C-4195-907D-06C96D0140A6::net8.0::Tests.ErrorOr.AggregationExtensionsTests.AppendErrors_ShouldHandleNullErrorInEachItemGracefully_WithUserType&lt;/TestId&gt;&#xD;
&lt;TestId&gt;xUnit::0E84EC69-4E4C-4195-907D-06C96D0140A6::net8.0::Tests.ErrorOr.AggregationExtensionsTests.AppendErrors_WithAdditionalErrors_AppendsErrorsCorrectly&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=fc72423c_002Da058_002D407c_002D9b56_002Dca48a86792c3/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="AggregationExtensionsTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::0E84EC69-4E4C-4195-907D-06C96D0140A6::net8.0::Tests.ErrorOr.AggregationExtensionsTests&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
- [`Else`](#else)
- [`Else`](#else-1)
- [`ElseAsync`](#elseasync)
- [`AppendErrors`](#appenderrors)
- [Mixing Features (`Then`, `FailIf`, `Else`, `Switch`, `Match`)](#mixing-features-then-failif-else-switch-match)
- [Error Types](#error-types)
- [Built in error types](#built-in-error-types)
Expand All @@ -76,6 +77,8 @@ Loving it? Show your support by giving this project a star!

This 👇



```cs
public float Divide(int a, int b)
{
Expand Down Expand Up @@ -567,6 +570,34 @@ ErrorOr<string> foo = await result
.ElseAsync(errors => Task.FromResult($"{errors.Count} errors occurred."));
```

## `AppendErrors`

The `AppendErrors` method allows you to add additional errors to an existing `ErrorOr` result.

```cs

ErrorOr result = Error.Validation(description: "Initial error");

/**
* Appends additional errors to the existing ErrorOr object.
*
* @param errors A list of errors to append.
* @return The updated ErrorOr object with the appended errors.
*/
result = result.AppendErrors(new List<Error> { Error.Validation(description: "Additional error 1"), Error.Validation(description: "Additional error 2") });

if (result.IsError)
{
result.Errors.ForEach(error => Console.WriteLine(error.Description));
// Output:
// Initial error
// Additional error 1
// Additional error 2
}

```


# Mixing Features (`Then`, `FailIf`, `Else`, `Switch`, `Match`)

You can mix `Then`, `FailIf`, `Else`, `Switch` and `Match` methods together.
Expand Down Expand Up @@ -706,6 +737,7 @@ public ErrorOr<float> Divide(int a, int b)
}
```


# [Mediator](https://github.com/jbogard/MediatR) + [FluentValidation](https://github.com/FluentValidation/FluentValidation) + `ErrorOr` 🤝

A common approach when using `MediatR` is to use `FluentValidation` to validate the request before it reaches the handler.
Expand Down Expand Up @@ -753,6 +785,41 @@ public class ValidationBehavior<TRequest, TResponse>(IValidator<TRequest>? valid
}
```

# Enhancements

## Creating `ErrorOr` instances from tuples and custom objects

The `ErrorOrFactory` class now includes additional factory methods to create `ErrorOr` instances from tuples and custom objects. This provides more flexibility and convenience when creating `ErrorOr` instances.

### Example

```cs
var errorOr = ErrorOrFactory.FromTuple((1, "value"));
var errorOr = ErrorOrFactory.FromCustomObject(new CustomObject { Id = 1, Name = "value" });
```

## Improved error handling

The `ErrorType` enum now includes more specific error types for granular error handling. The `Error` struct also includes corresponding factory methods for the new error types.

### Example

```cs
var error = Error.ValidationError("Validation error occurred");
var error = Error.DatabaseError("Database error occurred");
```

## Additional extension methods

The `ErrorOr` extension files now include additional extension methods for enhanced functionality. These methods provide additional functionality, such as chaining operations, transforming values, or handling errors in different ways.

### Example

```cs
var result = errorOr.Then(value => value * 2)
.Else(errors => Error.Unexpected("Unexpected error occurred"));
```

# Contribution 🤲

If you have any questions, comments, or suggestions, please open an issue or create a pull request 🙂
Expand All @@ -763,4 +830,4 @@ If you have any questions, comments, or suggestions, please open an issue or cre

# License 🪪

This project is licensed under the terms of the [MIT](https://github.com/mantinband/error-or/blob/main/LICENSE) license.
This project is licensed under the terms of the [MIT](://github.com/mantinband/error-or/blob/main/LICENSE) license.
79 changes: 79 additions & 0 deletions src/ErrorOr/ErrorOr.AggregationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace ErrorOr.ErrorOr
{
public static partial class AggregationExtensions
{
/// <summary>
/// Appends errors from multiple <see cref="IErrorOr"/> instances into the current <see cref="ErrorOr{TValue}"/> instance.
/// </summary>
/// <typeparam name="TValue">The type of the underlying value in the <paramref name="errorOr"/>.</typeparam>
/// <param name="errorOr">The current <see cref="ErrorOr{TValue}"/> instance.</param>
/// <param name="errors">The additional <see cref="IErrorOr"/> instances whose errors need to be appended.</param>
/// <returns>A new <see cref="ErrorOr{TValue}"/> instance containing all combined errors, or the original if no errors are added.</returns>
public static ErrorOr<TValue> AppendErrors<TValue>(
this ErrorOr<TValue> errorOr,
params IErrorOr[]? errors)
{
// Guard clause to handle null or empty errors array.
if (errors is null || errors.Length == 0)
{
return errorOr;
}

// Start with the errors from the current ErrorOr instance.
var combinedErrors = new List<Error>(errorOr.ErrorsOrEmptyList);

// Add errors from the additional ErrorOr instances if they contain errors.
foreach (var error in errors)
{
if (error?.IsError == true)
{
// Add errors from error instances that are in an error state
combinedErrors.AddRange(error.Errors ?? Enumerable.Empty<Error>());
}
}

// If there are errors, return a new ErrorOr<TValue> instance with those errors.
return combinedErrors.Count > 0 ? ErrorOr<TValue>.FromError(combinedErrors) : errorOr;
}

/// <summary>
/// Appends errors from multiple <see cref="IErrorOr"/> instances into the current <see cref="ErrorOr{TValue}"/> instance,
/// and includes additional metadata for enhanced error handling.
/// </summary>
/// <typeparam name="TValue">The type of the underlying value in the <paramref name="errorOr"/>.</typeparam>
/// <param name="errorOr">The current <see cref="ErrorOr{TValue}"/> instance.</param>
/// <param name="metadata">Additional metadata to include with the errors.</param>
/// <param name="errors">The additional <see cref="IErrorOr"/> instances whose errors need to be appended.</param>
/// <returns>A new <see cref="ErrorOr{TValue}"/> instance containing all combined errors with metadata, or the original if no errors are added.</returns>
public static ErrorOr<TValue> AppendErrorsWithMetadata<TValue>(
this ErrorOr<TValue> errorOr,
Dictionary<string, object> metadata,
params IErrorOr[]? errors)
{
// Guard clause to handle null or empty errors array.
if (errors is null || errors.Length == 0)
{
return errorOr;
}

// Start with the errors from the current ErrorOr instance.
var combinedErrors = new List<Error>(errorOr.ErrorsOrEmptyList);

// Add errors from the additional ErrorOr instances if they contain errors.
foreach (var error in errors)
{
if (error?.IsError == true)
{
// Add errors from error instances that are in an error state
combinedErrors.AddRange(error.Errors ?? Enumerable.Empty<Error>());
}
}

// Add metadata to each error
var enhancedErrors = combinedErrors.Select(e => e with { Metadata = metadata }).ToList();

// If there are errors, return a new ErrorOr<TValue> instance with those errors and metadata.
return enhancedErrors.Count > 0 ? ErrorOr<TValue>.FromError(enhancedErrors) : errorOr;
}
}
}
Loading