Skip to content

Commit

Permalink
Result Pattern Refactoring + Errors Refactoring (#9)
Browse files Browse the repository at this point in the history
* refactored result pattern, added async variations
* refactored errors
* added business rules
* added static errors
* refactored business logic to use rules and static errors
* added testing problem details
  • Loading branch information
skrasekmichael authored Feb 11, 2024
1 parent 356a4a2 commit 6667d86
Show file tree
Hide file tree
Showing 37 changed files with 577 additions and 390 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public async Task<Result<TeamId>> Handle(CreateTeamCommand request, Cancellation
{
var user = await _userRepository.GetUserByIdAsync(request.OwnerId, ct);
return await user
.EnsureNotNull(NotFoundError.New("Account does not exist."))
.Map(user => Team.Create(request.Name, user, _dateTimeProvider))
.EnsureNotNull(UserErrors.AccountNotFound)
.Then(user => Team.Create(request.Name, user, _dateTimeProvider))
.Tap(_teamRepository.AddTeam)
.Map(team => team.Id)
.Then(team => team.Id)
.TapAsync(_ => _unitOfWork.SaveChangesAsync(ct));
}
}
7 changes: 3 additions & 4 deletions src/TeamUp.Application/Teams/GetTeam/GetTeamQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using TeamUp.Application.Abstractions;
using TeamUp.Common;
using TeamUp.Contracts.Teams;
using TeamUp.Domain.Aggregates.Teams;

namespace TeamUp.Application.Teams.GetTeam;

Expand Down Expand Up @@ -36,9 +37,7 @@ public async Task<Result<TeamResponse>> Handle(GetTeamQuery request, Cancellatio
.FirstOrDefaultAsync(ct);

return team
.EnsureNotNull(NotFoundError.New("Team not found."))
.Ensure(
team => team.Members.Any(member => member.UserId == request.InitiatorId),
AuthorizationError.New("Not member of the team."));
.EnsureNotNull(TeamErrors.TeamNotFound)
.Ensure(team => team.Members.Any(member => member.UserId == request.InitiatorId), TeamErrors.NotMemberOfTeam);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ public ActivateAccountCommandHandler(IUserRepository userRepository, IUnitOfWork
public async Task<Result> Handle(ActivateAccountCommand request, CancellationToken ct)
{
var user = await _userRepository.GetUserByIdAsync(request.UserId, ct);
if (user is null)
return NotFoundError.New("User not found");

user.Activate();
await _unitOfWork.SaveChangesAsync(ct);

return Result.Success;
return await user
.EnsureNotNull(UserErrors.UserNotFound)
.Tap(user => user.Activate())
.TapAsync(_ => _unitOfWork.SaveChangesAsync(ct))
.ToResultAsync();
}
}
9 changes: 9 additions & 0 deletions src/TeamUp.Application/Users/AuthenticationErrors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using TeamUp.Common;

namespace TeamUp.Application.Users;

public static class AuthenticationErrors
{
public static readonly AuthenticationError InvalidCredentials = AuthenticationError.New("Invalid Credentials.", "Auth.InvalidCredentials");
public static readonly AuthenticationError NotActivatedAccount = AuthenticationError.New("Account is not activated.", "Auth.NotActivatedAccount");
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using TeamUp.Application.Abstractions;
using TeamUp.Common;
using TeamUp.Contracts.Users;
using TeamUp.Domain.Aggregates.Users;

namespace TeamUp.Application.Users.GetUserDetail;

Expand All @@ -24,6 +25,6 @@ public async Task<Result<UserResponse>> Handle(GetUserDetailsQuery request, Canc
.Select(user => _mapper.ToResponse(user))
.FirstOrDefaultAsync(ct);

return user.EnsureNotNull(NotFoundError.New("User not found."));
return user.EnsureNotNull(UserErrors.UserNotFound);
}
}
15 changes: 5 additions & 10 deletions src/TeamUp.Application/Users/Login/LoginCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,10 @@ public LoginCommandHandler(IUserRepository userRepository, IPasswordService pass
public async Task<Result<string>> Handle(LoginCommand request, CancellationToken ct)
{
var user = await _userRepository.GetUserByEmailAsync(request.Email, ct);
if (user is null)
return AuthenticationError.New("Invalid Credentials");

if (!_passwordService.VerifyPassword(request.Password, user.Password))
return AuthenticationError.New("Invalid Credentials");

if (user.Status != UserStatus.Activated)
return AuthenticationError.New("Account is not activated");

return _tokenService.GenerateToken(user);
return user
.EnsureNotNull(AuthenticationErrors.InvalidCredentials)
.Ensure(user => _passwordService.VerifyPassword(request.Password, user.Password), AuthenticationErrors.InvalidCredentials)
.Ensure(user => user.Status == UserStatus.Activated, AuthenticationErrors.NotActivatedAccount)
.Then(user => _tokenService.GenerateToken(user));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,24 @@ namespace TeamUp.Application.Users.Register;

internal sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand, Result<UserId>>
{
private readonly IUserRepository _userRepository;
private readonly UserFactory _userFactory;
private readonly IPasswordService _passwordService;
private readonly IUnitOfWork _unitOfWork;

public RegisterUserCommandHandler(IUserRepository userRepository, IPasswordService passwordService, IUnitOfWork unitOfWork)
public RegisterUserCommandHandler(UserFactory userFactory, IPasswordService passwordService, IUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_userFactory = userFactory;
_passwordService = passwordService;
_unitOfWork = unitOfWork;
}

public async Task<Result<UserId>> Handle(RegisterUserCommand request, CancellationToken ct)
{
var password = _passwordService.HashPassword(request.Password);
var user = User.Create(request.Name, request.Email, password);
var user = await _userFactory.CreateAndAddUserAsync(request.Name, request.Email, password, ct);

if (await _userRepository.ConflictingUserExistsAsync(user))
return ConflictError.New("User with this email is already registered.");

_userRepository.AddUser(user);
await _unitOfWork.SaveChangesAsync(ct);

return user.Id;
return await user
.Then(user => user.Id)
.TapAsync(_ => _unitOfWork.SaveChangesAsync(ct));
}
}
199 changes: 0 additions & 199 deletions src/TeamUp.Common/Result.cs

This file was deleted.

15 changes: 6 additions & 9 deletions src/TeamUp.Common/Error.cs → src/TeamUp.Common/Result/Error.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
using System.Runtime.CompilerServices;

namespace TeamUp.Common;
namespace TeamUp.Common;

public abstract record ErrorBase
{
public string Code { get; internal init; } = null!;
public string Message { get; internal init; } = null!;
public string Code { get; protected init; } = null!;
public string Message { get; protected init; } = null!;
}

public abstract record Error<TSelf> : ErrorBase where TSelf : ErrorBase, new()
public abstract record Error<TSelf> : ErrorBase where TSelf : Error<TSelf>, new()
{
public static TSelf New(string message, [CallerMemberName] string? caller = null, [CallerFilePath] string? filePath = null)
public static TSelf New(string message, string code = "")
{
var className = Path.GetFileNameWithoutExtension(filePath);
return new TSelf()
{
Code = $"{className}.{caller}",
Code = code,
Message = message
};
}
Expand Down
Loading

0 comments on commit 6667d86

Please sign in to comment.