seangwright / typescript-functional-extensions

A TypeScript implementation of the C# library CSharpFunctionalExtensions, including synchronous and asynchronous Maybe and Result monads.
MIT License
33 stars 4 forks source link

feat(result): support combining multiple results #8

Closed GregOnNet closed 2 years ago

GregOnNet commented 2 years ago

Description

Allows combining multiple results into one. Error Message of failing results are concatenated.

CSharp Functional Extensions provide a similar feature: https://github.com/vkhorikov/CSharpFunctionalExtensions/blob/master/CSharpFunctionalExtensions/Result/Methods/Combine.cs#L18

Motivation

If you are using Value Objects to save your domain from invalid values, having a comfortable way of checking all used Value Objects leads to code that is more readable:


const company = Company.create(dto.companyId);
const profileAndUser = ProfileAndUser.create(dto.profileId, dto.userId);
const transportationOrder = TransportationOrder.create(dto.transportationOrderReference, dto.transportationOrderReference);

- if (company.isFailure) {
-     throw new RpcException(company.getErrorOrThrow());
-   }
-
-  if (profileAndUser.isFailure) {
-    throw new RpcException(profileAndUser.getErrorOrThrow());
-  }
-
- if (transportationOrder.isFailure) {
-   throw new RpcException(transportationOrder.getErrorOrThrow());
- }

+ const result = Result.combine({ company, profileAndUser, transportationOrder })
+
+ if (result.isFailure) {
+   throw new RpcException(result.getErrorOrThrow());
+ }

Open questions

Currently, only ErrorMessages of type string are supported. Do we want to invest some more time to aggregate Error-Objects as well?

GregOnNet commented 2 years ago

Idea Maybe, we could improve .combine even more providing a better experience.

const user = Result.success({ name: 'Somebody' });
const order = Result.success({ name: 'Some Name' });

const result = Result.combine({ user, oder }).map(({ user, order }) => ... )
//                                                           ^ Result value combines values of all Results.
GregOnNet commented 2 years ago

... Working on better typings as seen for the pipe-Operator

GregOnNet commented 2 years ago

Added types and a few tests.

KyleMcMaster commented 2 years ago

Nice readable tests! 👍

GregOnNet commented 2 years ago

Introducing Record in favour of using tuples. Reason: Accessing members of a Record is more readable.

// Current Implementation
Result.combine( result1, result2).map(results => results[0] ...)

// vs. Record

Result.combine({ result1, result2 }).map(results => results.result1)
GregOnNet commented 2 years ago

Thanks, @KyleMcMaster, I updated the implementation.

Let me know if I can improve something.

GregOnNet commented 2 years ago

Hi @seangwright & @KyleMcMaster,

the PR is ready for review. 👍