refactor: replace Ardalis.Result with ErrorOr across the app

Ardalis Result<T> carried incoherent success sub-statuses and a clunky
generic converter; ErrorOr models success-or-errors cleanly.

- Application: all commands/queries/handlers now return ErrorOr<T>
  (non-generic Result -> ErrorOr<Success>); errors via Error.NotFound/
  Conflict/Validation/Unauthorized/Failure; success via value or new Success()
- Drop ResultExtensions.As<>(): error propagation is now just return .Errors
- Rewrite HTTP mapping (ErrorOrHttpExtensions) off Error.Type; success maps
  to 200 (value) or 204 (Success); Created still built in the endpoints
- Endpoints/gateway middleware: !IsSuccess -> IsError
- Swap Ardalis.Result package for ErrorOr 2.1.1 (remove unused ref from Worker)

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 04:05:43 +02:00
co-authored by Claude Opus 4.8
parent a3bf796f17
commit 6e469dbb4a
56 changed files with 259 additions and 283 deletions
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Musify.Application.Users.Responses;
using Musify.Domain.Entities;
@@ -9,7 +9,7 @@ namespace Musify.Application.Users.Commands
string Name,
string? FirstName,
string? SecondName)
: ICommand<Result<UserApplicationResponse>>
: ICommand<ErrorOr<UserApplicationResponse>>
{
public static User ToEntity(CreateUserCommand command)
{
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
namespace Musify.Application.Users.Commands
@@ -9,5 +9,5 @@ namespace Musify.Application.Users.Commands
string? FirstName,
string? SecondName,
string? ProfilePictureUrl)
: ICommand<Result>;
: ICommand<ErrorOr<Success>>;
}
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -9,15 +9,15 @@ using Musify.Application.Users.Responses;
namespace Musify.Application.Users.Handlers
{
public class CreateUserCommandHandler(IDatabase database, ILogger<CreateUserCommandHandler> logger)
: ICommandHandler<CreateUserCommand, Result<UserApplicationResponse> >
: ICommandHandler<CreateUserCommand, ErrorOr<UserApplicationResponse> >
{
public async ValueTask<Result<UserApplicationResponse>> Handle(CreateUserCommand request, CancellationToken cancellationToken)
public async ValueTask<ErrorOr<UserApplicationResponse>> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var userExists = await database.Users.AnyAsync(u => u.Id == request.Id, cancellationToken);
if (userExists)
{
logger.LogInformation("User {UserId} already exists", request.Id);
return Result<UserApplicationResponse>.Conflict($"User {request.Id} already exists");
return Error.Conflict(description: $"User {request.Id} already exists");
}
var newUser = CreateUserCommand.ToEntity(request);
@@ -30,11 +30,11 @@ namespace Musify.Application.Users.Handlers
catch (Exception exception)
{
logger.LogError(exception, "Failed to create user {UserId}", request.Id);
return Result.Error($"Failed to create user {request.Id}");
return Error.Failure(description: $"Failed to create user {request.Id}");
}
logger.LogInformation("Created user {UserId}", newUser.Id);
return Result.Created(UserApplicationResponse.FromEntity(newUser));
return UserApplicationResponse.FromEntity(newUser);
}
}
}
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
@@ -8,9 +8,9 @@ using Musify.Application.Users.Responses;
namespace Musify.Application.Users.Handlers
{
public class GetUserByIdQueryHandler(IDatabase database)
: IQueryHandler<GetUserByIdQuery, Result<UserApplicationResponse> >
: IQueryHandler<GetUserByIdQuery, ErrorOr<UserApplicationResponse> >
{
public async ValueTask<Result<UserApplicationResponse>> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
public async ValueTask<ErrorOr<UserApplicationResponse>> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await database.Users
.AsNoTracking()
@@ -18,9 +18,10 @@ namespace Musify.Application.Users.Handlers
.Select(u => UserApplicationResponse.FromEntity(u))
.SingleOrDefaultAsync(cancellationToken);
return user is null
? Result<UserApplicationResponse>.NotFound()
: Result.Success(user);
if (user is null)
return Error.NotFound();
return user;
}
}
}
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Pagination;
@@ -10,9 +10,9 @@ using Musify.Application.Contracts;
namespace Musify.Application.Users.Handlers
{
public class GetUsersQueryHandler(IDatabase database)
: IQueryHandler<GetUsersQuery, Result<PaginatedResponse<UserApplicationResponse> > >
: IQueryHandler<GetUsersQuery, ErrorOr<PaginatedResponse<UserApplicationResponse> > >
{
public async ValueTask<Result<PaginatedResponse<UserApplicationResponse> >> Handle(GetUsersQuery request, CancellationToken cancellationToken)
public async ValueTask<ErrorOr<PaginatedResponse<UserApplicationResponse> >> Handle(GetUsersQuery request, CancellationToken cancellationToken)
{
var user = database.Users
.AsNoTracking()
@@ -29,7 +29,7 @@ namespace Musify.Application.Users.Handlers
.Select(u => UserApplicationResponse.FromEntity(u))
.ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<UserApplicationResponse>.FromPagedList(pagedUser));
return PaginatedResponse<UserApplicationResponse>.FromPagedList(pagedUser);
}
}
}
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -9,9 +9,9 @@ using Musify.Domain.Entities;
namespace Musify.Application.Users.Handlers
{
public class SyncUserCommandHandler(IDatabase database, ILogger<SyncUserCommandHandler> logger)
: ICommandHandler<SyncUserCommand, Result>
: ICommandHandler<SyncUserCommand, ErrorOr<Success>>
{
public async ValueTask<Result> Handle(SyncUserCommand request, CancellationToken cancellationToken)
public async ValueTask<ErrorOr<Success>> Handle(SyncUserCommand request, CancellationToken cancellationToken)
{
var user = await database.Users
.SingleOrDefaultAsync(u => u.Id == request.Id, cancellationToken);
@@ -31,7 +31,7 @@ namespace Musify.Application.Users.Handlers
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Provisioned user {UserId}", request.Id);
return Result.Success();
return new Success();
}
user.Name = request.Name;
@@ -44,7 +44,7 @@ namespace Musify.Application.Users.Handlers
if (written > 0)
logger.LogInformation("Synced profile for user {UserId}", request.Id);
return Result.Success();
return new Success();
}
}
}
@@ -1,9 +1,9 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Musify.Application.Users.Responses;
namespace Musify.Application.Users.Queries
{
public record GetUserByIdQuery(long Id)
: IQuery<Result<UserApplicationResponse> >;
: IQuery<ErrorOr<UserApplicationResponse> >;
}
@@ -1,4 +1,4 @@
using Ardalis.Result;
using ErrorOr;
using Mediator;
using Musify.Application.Pagination;
using Musify.Application.Users.Responses;
@@ -6,5 +6,5 @@ using Musify.Application.Users.Responses;
namespace Musify.Application.Users.Queries
{
public record GetUsersQuery(int PageNumber, int PageSize, string? UsernameSearch = null)
: IQuery<Result<PaginatedResponse<UserApplicationResponse> >>;
: IQuery<ErrorOr<PaginatedResponse<UserApplicationResponse> >>;
}