amantinband / error-or Goto Github PK
View Code? Open in Web Editor NEWA simple, fluent discriminated union of an error or a result.
License: MIT License
A simple, fluent discriminated union of an error or a result.
License: MIT License
It would be nice to have an optional dictionary for errors, next to the errorCode, message,... So it would be possible to provide addtional informations with an error.
Hi @amantinband,
Thanks for this great library. I'm currently having an issue when I try to deserialize ErrorOr type. ErrorOr values seem to be serializable but not deserializable.
Sample code:
using ErrorOr;
using System.Text.Json;
ErrorOr<int> e1 = 42;
ErrorOr<int> e2 = Error.NotFound();
Console.WriteLine($"e1: {e1.Value}/{e1.IsError}");
Console.WriteLine($"e2: {e2.Value}/{e2.IsError}");
var j1 = JsonSerializer.Serialize(e1);
var j2 = JsonSerializer.Serialize(e2);
Console.WriteLine("------");
Console.WriteLine($"j1: {j1}");
Console.WriteLine($"j2: {j2}");
e1 = JsonSerializer.Deserialize<ErrorOr<int>>(j1);
e2 = JsonSerializer.Deserialize<ErrorOr<int>>(j2);
Console.WriteLine("------");
Console.WriteLine($"e1: {e1.Value}/{e1.IsError}");
Console.WriteLine($"e2: {e2.Value}/{e2.IsError}");
Output:
e1: 42/False
e2: 0/True
------
j1: {"IsError":false,"Errors":[{"Code":"ErrorOr.NoErrors","Description":"Error list cannot be retrieved from a successful ErrorOr.","Type":1,"NumericType":1}],"ErrorsOrEmptyList":[],"Value":42,"FirstError":{"Code":"ErrorOr.NoFirstError","Description":"First error cannot be retrieved from a successful ErrorOr.","Type":1,"NumericType":1}}
j2: {"IsError":true,"Errors":[{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}],"ErrorsOrEmptyList":[{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}],"Value":0,"FirstError":{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}}
------
e1: 0/False
e2: 0/False
This problem appears to be caused by getter-only properties. I think serializers are not smart enough to understand where the data comes from. I created a pull request by the way. If you have any better solution, feel free to reject it.
Thanks!
Versions
I've encountered this issue on:
ErrorOr version : 1.2.1
.NET version: .NET 7
OS version : Windows 11
It would be great to have all the type of errors that could be mapped to 4xx status code series
Is there any reason you haven't included a .NET Standard 2.0 target as there appears to be no issue compiling it for that target, would also help with more broader usage of the lib?
Hi.
I am writing LoggingPipelineBehavior using MediatR.
Here's class:
internal class LoggingPipelineBehavior<TRequest, TResponse> :
IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : IErrorOr
And here's my Handle method:
public async Task<TResponse> Handle(TRequest request, CancellationToken _, RequestHandlerDelegate<TResponse> next)
{
LogRequest(request);
TResponse response = await next();
if (response is IErrorOr errorOr && errorOr.Errors?.Count > 0)
{
LogErrors(errorOr.Errors);
return response;
}
LogResponse(response);
return response;
}
Unfortunately, if my handler is returning successful object (not errors), I'm still going inside that if is IErrorOr block, because if ErrorOr.IsError is false, Errors's Count is still getting 1 (NoErrors error).
Would it be possible to add
bool IsError { get; }
to IErrorOr interface, so it can be checked by this flag if this object is successful or failed one?
Edit: Also, would it be possible to add
object Value { get; }
to interface to retrieve successful instance?
If there's other/better solution, please let me know.
By the way - great package!
Thanks,
Sลawek
https://github.com/amantinband/error-or/blob/main/src/Error.cs type doesn't have any property or field that can be changed after creation. So why not just mark it with readonly?
Hello,
one thing FluentResults has is the ability to add metadata. After switching to ErrorOr, I have to add the metadata inside ProblemDetailsFactory
, which is very cumbersome... Due to this libraries implementation I have not find a elegant way to attach metadata to the error directly.
Do you know a way to add metadata in a similar way like in FluentResults or another generic way I can use to add metadata to Error
. Or is it maybe even possible to add metadata to ErrorOr (maybe via generic Error
instead of hardcoded one...).
Thanks in advance ๐
This is more of a question than an issue. I use ErrorOr to guard my calculation engine from getting faulty data and it works great. I usually do this by making constructors private and my objects immutable and allow object creation only in form of
public static ErrorOr<T> TryCreate(parameters...);
I now have some cases where I would like to change one or two properties of the object. For C# record classes this is solved very elegantly by using with expresisons:
var person2 = person1 with {Lastname='Doe' };
The drawback of with-expressions is that in this case I cannot run all my validation logic.
In some cases I decided to create a copy constructor-like version of TryCreate() manually to still get an ErrorOr and all it's validation logic but writing copy constructors is not something I enjoy doing. This looks something like this:
public static ErrorOr<MyClass> TryCreate(MyClass orig, int? id= null, string? name= null)
{
id ??= orig.Id;
name ??= orig.Name;
return TryCreate(id.Value, name.Value);
}
// can be called with named parameters like so:
var y = TryCreate(myClassObj, name:"Bill");
While this consuming part is okay, the writing part of the copy constructors is something I would want to avoid.
I wonder if there is a more elegant solution (which does not involve Reflections). I think it could be done with code generation but that's something I havent't done before.
would appreciate ideas on this topic.
It would be great to have covariant interface instead of non-generic like this:
public interface IErrorOr<out TValue>
{
List<Error>? Errors { get; }
bool IsError { get; }
List<Error> ErrorsOrEmptyList { get; }
Error FirstError { get; }
TValue Value { get; }
}
Then it allows us to upcast ErrorOr objects like this:
ErrorOr<TChild> childResult = new TChild();
IErrorOr<TParent> parentResult = childResult;
Hi, thank You for this awesome library.
I'm having trouble implementing a get request handler, the code as follow:
public class GetQueryHandler :
IRequestHandler<GetQuery, ErrorOr<IQueryable<User>>>
{
private readonly IApplicationDbContext _context;
public GetQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<ErrorOr<IQueryable<User>>> Handle(
GetQuery request,
CancellationToken cancellationToken)
{
await Task.CompletedTask;
return _context.Users.AsQueryable();
}
}
Throws the error cannot implicitly convert type IQueryable to ErrorOr.ErrorOr<IQueryable>.
If I change the IQueryable to IList and change the return to _context.Users.ToList() it works.
What I want to achieve is something similar as:
from Hellang.Middleware.ProblemDetails
Now, the problem is I should only expose the exception data with certain environments so set the exception on an Error type in the repository and choose to show it in ... the ApiProblemDetailsFactory?
{
var response = await _orderClient.SummaryAsync(orderNumber);
if(_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug("Retrieved shipping information for order {orderNumber}", orderNumber);
return _mapper.Map<Domain.Shipping.ShippingInformation.ShippingInformation>(response);
}
catch (ApiClientCallException apiClientCallException) when (apiClientCallException.StatusCode == 404)
{
if(_logger.IsEnabled(LogLevel.Information))
_logger.LogInformation("Order {orderNumber} not found", orderNumber);
return Errors.Shipping.OrderNotFound;
}
catch (Exception e)
{
if(_logger.IsEnabled(LogLevel.Error))
_logger.LogError(e, "Failed to get shipping information for order {orderNumber}", orderNumber);
return Error.Unexpected(); // Add exception here!
}
I have this code from my RecipeService.cs
.
public async Task<ErrorOr<Recipe>> GetAsync(string id)
{
var recipe = await _recipeRepo.GetAsync(id);
if (recipe is null)
{
return Errors.Recipe.NotFound;
}
return recipe;
}
And also I have this code from my RecipeController.cs
.
public async Task<IActionResult> GetRecipes([FromQuery][Required] string id)
{
ErrorOr<Recipe> recipes = await _recipeService.GetAsync(id);
return recipes.Match(
recipe => Ok(recipe),
errors => Problem(errors)
);
}
This is my startup configuration.
var app = builder.Build();
{
app.UseSwagger();
app.UseSwaggerUI();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error"); // I want this to activate if the environment is not in Development.
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
What I want to achieve is for the REST API to return a stack trace for a specific error instead of returning a custom error. I only want it to return a custom error when I set my environment to Production.
ErrorOr<Dictionary<int, int>> priorityResult = ..... where inside I return Error.Failure("... and method returns
after checking
if (priorityResult.IsError)
return priorityResult.Errors;
IsError always false!
Hi,
many thanks for this tool,
Please see the code the below from your examples:
ErrorOr DeleteUser(Guid id)
{
var user = await _userRepository.GetByIdAsync(id);
if (user is null)
{
return Error.NotFound(code: "User.NotFound", description: "User not found.");
}
await _userRepository.DeleteAsync(user);
return Result.Deleted;
}
How do you capture an error from this line: await _userRepository.DeleteAsync(user); Using your tool so that you can include it in the returned results to the front end to inform the user that the record can not be deleted?
Many thanks
Zak
Say that you are processing a collection of items: for each item, you need to contact an external service accessible under authentication.
There are then different levels of errors: "fatal", for errors as "authentication failed" (this error will occur for each item in the list), and "warning", such as "this item does not exist".
We should be able to handle errors based on their levels
Having a validation depending on multiple checks, is constructing a list of errors the only way to return a failure result for multiple fails?
Having a Bind extension method or something titled more idiomatic to C# would be nice, since this would enable to chain multiple operations in a single flow.
Can you please add a "StrackTrace" string Property to the Error struct when creating a new exception. this will help with logging the errors and handling them.
Hi,
Please add strong name for the assembly. Without it, in 4.x .NET projects without strong name it is not possible to use this library, getting exception:
Could not load file or assembly 'ErrorOr, Version=1.2.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. A strongly-named assembly is required. (Exception from HRESULT: 0x80131044)
System.IO.FileLoadException: Could not load file or assembly 'ErrorOr, Version=1.2.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. A strongly-named assembly is required. (Exception from HRESULT: 0x80131044)
First of all, I love this extension.
I would like to see extension methods to convert an ErrorOr (domain result) to an IActionResult or IResult.
The conversion should send 200OK or 204NoContent if the operation is successfull.
If the result is an Error the conversion would take the type of error and return an appropriate ProblemDetails with the correct status Code.
This enhancement was inspired by this repository https://github.com/AKlaus/DomainResult
First of all, thanks for this awesome project!
can you please sign this NuGet with a strong name? this will help to integrate it in a strong name solution.
Would be nice if there could be a IsSuccess Property like the IsError Property.
Status code always returns 500 in Error.Custom() and Error.Failure()
First of all, thank you very much for creating and maintaining this repository.
I would like to suggest adding an additional built-in error type, namely 'Forbidden,' because I believe that in some situations, it provides a better description than the existing 'Unauthorized.'
I understand that error types don't necessarily have to directly align with HTTP standards, but in this case, it might make more sense. This is what the specifications state:
Unauthorized
The 401 (Unauthorized) status code indicates that the request has not
been applied because it lacks valid authentication credentials for
the target resource. The server generating a 401 response MUST send
a WWW-Authenticate header field (Section 4.1) containing at least one
challenge applicable to the target resource.
If the request included authentication credentials, then the 401
response indicates that authorization has been refused for those
credentials. The user agent MAY repeat the request with a new or
replaced Authorization header field (Section 4.2). If the 401
response contains the same challenge as the prior response, and the
user agent has already attempted authentication at least once, then
the user agent SHOULD present the enclosed representation to the
user, since it usually contains relevant diagnostic information.
Forbidden:
The 403 (Forbidden) status code indicates that the server understood
the request but refuses to authorize it. A server that wishes to
make public why the request has been forbidden can describe that
reason in the response payload (if any).
If authentication credentials were provided in the request, the
server considers them insufficient to grant access. The client
SHOULD NOT automatically repeat the request with the same
credentials. The client MAY repeat the request with new or different
credentials. However, a request might be forbidden for reasons
unrelated to the credentials.
If you agree with my reasoning, I could open a PR to add Forbidden as a built-in type.
I look forward to hearing from you.
edit:
I know I can potentially add a custom error type myself, but it seemed to me like a valuable addition as a built-in
Thanks for your nice videos.
How i can use this if i dont have to return anything except errors?
Hi, I like this library since it allows me to pull more and more logic into service classes rather than bloat my controllers. I also want my clients to be able to see possible outcomes of API endpoint calls by utilizing OpenAPI spec "Response" information. Currently I am using the SwaggerResponse attribute, but I watched your video about flow control and now I am trying to incorporate the idea of centralized errors into my app.
I was wondering if one can use the defined errors to annotate controller functions, e.g. like this:
[SwaggerResponseByError(Errors.Authentication.WrongCredentials)]
Do you think something like this can be achieved? Would you consider it sensible doing so? Thanks!
The example shown in the docs (https://github.com/amantinband/error-or#from-value-using-from) does not actually compile, and results in The type or namespace name 'From' does not exist in the namespace 'ErrorOr' (are you missing an assembly reference?)
. Currently we have to use ErrorOr.ErrorOr.From(data)
instead.
Hi @amantinband ,
Thank you very much for developing this library. Got to know about it from your video series.
Let me know your thoughts on adding a special error type ErrorType.Domain to represent errors related specific to business rules instead of just ErrorType.Custom. This is because, if its an Error it indeed means a type of Failure so instead of just ErrorType.Failure if we use ErrorType.Domain, it signifies failure in some use case / business rule validation.
Example, consider this business rule: User cannot add/upload more than 3 products to sell under his/her current FREE subscription.
Can you please share your thoughts on this.
Hi,
it would be nice to have a new value on the enum ErrorType to track down the NotAuthorized operations.
What is your thought? ๐
For some reason, when I return an empty list of Errors, I get IsError = true.
Maybe there's a better way to design this on my end, but I'd like to use ErrorOr methods to also check for null values and return null or at least IsError = false and Value = null when working with domain methods that do things like TryCreate.
Code Snippet:
/// <summary>
/// Ensures Positive Integer.
/// </summary>
public class PositiveInt : ValueObject
{
public int Value { get; }
public PositiveInt(int value, string propertyName)
{
value.Throw(paramName: propertyName).IfNegativeOrZero();
Value = value;
}
/// <summary>
/// Trys to create <see cref="PositiveInt"/>. Returns Error if it fails.
/// <remarks>When value is null, will return IsError = true with empty list of errors.</remarks>
/// </summary>
/// <param name="value">Value of <see cref="PositiveInt"/>.</param>
/// <param name="propertyName">Property <see cref="PositiveInt"/> is being assigned to.</param>
/// <returns><see cref="PositiveInt"/> or Validation Error.</returns>
public static ErrorOr<PositiveInt> TryCreate(int? value, string propertyName)
{
if (!value.HasValue)
{
return new List<Error>();
}
if (!int.IsPositive(value.Value))
{
return Error.Validation("PositiveInt.Invalid", $"{propertyName} must be larger than 0.");
}
return new PositiveInt(value.Value, propertyName);
}
/// <inheritdoc />
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Value;
}
}
When I create a test where the value property of TryCreate is null, I get IsError = true
and actual.ErrorsOrEmptyList.Should().BeEmpty() = true
.
What I'd ideally like to do is just return null, but then I'd have to make the return value for the method ErrorOr<PositiveInt>?
, which results in a lot of hassle. However, I also feel like I should be able to return an empty list of Error and IsError
should still be false?
I'm somewhat new to this library, so maybe there's a better way to do what I'm trying to accomplish. I'm open to suggestions.
Thanks!
Hi Amichai, I have a problem when trying to wrap an IEnumerable
to an ErrorOr<IEnumerable>
:
using ErrorOr;
class Foo {
public ErrorOr<IEnumerable<int>> wrap() {
IEnumerable<int> nums = new List<int>(){ 1, 2 };
return nums;
}
}
VSCode gives me an error:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<int>' to 'ErrorOr.ErrorOr<System.Collections.Generic.IEnumerable<int>>' [BuberDinner.Api]csharp(CS0029)
No quick fixes available
I find a workaround: changes IEnumerable<int> tools
to List<int> tools
:
using ErrorOr;
class Foo {
public ErrorOr<IEnumerable<int>> wrap() {
List<int> nums = new List<int>(){ 1, 2 };
return nums;
}
}
Awesome videos and NuGet package.
It would be nice to have a cancelled error type to handle the user cancelled scenarios for APIs etc
Is ErrorOr
designed so that you can mutate its state (List<Error> Errors
), or is there any other reason for this design choice?
Using IReadOnlyList<>
(or even IReadOnlyCollection<>
if accessing very first error is not needed) would improve (imo):
List<Error>
when accessing errors (though this should be relatively cheap, but still additional allocation)
ErrorOr(List<Error> errors)
)I'm really interested knowing specific reason (of course changing it to IReadOnly*
is breaking, obviously)
Scattered throughout our code base I see return types of ErrorOr. I think this comes from legacy code (pre the use of ErrorOr) where the bool indicated success or failure. It strikes me that the presence/absence of errors indicates the same. Is it possible to return ErrorOr (none-generic) so I can just test the IsError status to indicate success or failure? I guess it may be an issue with how to return the discriminated union for success because it then becomes ErrorOr!
Unless I'm missing something, it would be nice to be able to use both value and errors. In the specific scenario I'm imagining, one would try to "BatchUpdate" via the API:
The command handler runs through and verifies that each object exists in the database exists (or not). For each item that doesn't exist in the database, generate an error, and for each item that does exist, go ahead and update. The handler then returns both the successes and failures (errors), that can be mapped to a response.
Similar to another issue, this would allow us to use IsError to set the Success to false on the response, but show errors and success values.
Hi,
I tried to implement MeaditR Commands/Queries Validator with ErrorOr library. Follow your documentation [https://github.com/amantinband/error-or#dropping-the-exceptions-throwing-logic] with some little changes(in bold) ValidationBehavior class is below:
public class ValidationBehavior<TRequest, TResult> : IPipelineBehavior<TRequest, ErrorOr>
where TRequest : IRequest<ErrorOr>
{
private readonly IValidator? _validator;
public ValidationBehavior(IValidator<TRequest>? validator = null)
{
_validator = validator;
}
public async Task<ErrorOr<TResult>> Handle(
TRequest request,
CancellationToken cancellationToken,
RequestHandlerDelegate<ErrorOr<TResult>> next)
{
if (_validator == null)
{
return await next();
}
var validationResult = **await** _validator.**ValidateAsync**(request);
if (validationResult.**IsValid**)
{
return validationResult.Errors
.ConvertAll(validationFailure => Error.Validation(
code: validationFailure.PropertyName,
description: validationFailure.ErrorMessage));
}
return await next();
}
}
But above mentioned class never triggered.
Static class ConfigureServices.cs where is services registered is below:
using System.Reflection;
using FluentValidation;
using MediatR;
namespace Microsoft.Extensions.DependencyInjection;
public static class ConfigureServices
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
return services;
}
}
Please can you give me advice how to properly register ValidationBehavior class ?
Thanks in advance. :-)
Hi,
I think it's more an overall question/suggestion :-).
Was this library also created to replace null return types in my application or just to prevent to throw exceptions and get better Error Results?
Because if I would use this to prevent null return types I think the basic naming "ErrorOr" is not the best, when you read "everywhere" in your ValueObjects oder DomainLogic "ErrorOr<>" return type looks strange.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.