I have a method that looks for a loyalty card in the database, and returns it if it was found and is valid, or an enum value if not. This allows the calling code to switch on the enum value. I want to return an Either<TransactionRequestStates, Card>, as the method will be bound along with several similar methods that fetch and verify the incoming data. As database calls should be made async, I'll need to return EitherAsync as opposed to just Either.
To clarify, I have several methods, each of which performs a particular step. If successful, each method returns a Right containing some data that is used by later methods. If not, it returns a Left with an enum value indicating what went wrong. I want to compose them as follows (simplified for clarity)...
public static async Task<TransactionRequestStates> Process(
IHttpContextAccessor httpContextAccessor,
AppDbContext context, UserManager<User> userMgr,
string cardSerialNumber, string uri, int amount, string hash) {
Either<TransactionRequestStates, TransactionRequestStates> result = await (
from card in GetCard(context, cardSerialNumber, amount)
from deviceSerialNumber in GetDeviceSerialNumberFromToken(httpContextAccessor, context, userMgr)
from device in GetDevice(httpContextAccessor, context, deviceSerialNumber)
from rental in GetOrganisationRental(context, device)
from vcResponse in GetVcResponse(rental, card, amount, hash, uri)
from saveResponse in SaveResponse()
select TransactionRequestStates.Success
);
return result.Match(_ => TransactionRequestStates.Success, msg => msg);
}
This question covers the GetCard method, as I assume that if I can get that working, the others will all follow a similar pattern.
I can do the following...
private static EitherAsync<TransactionRequestStates, Card> GetCard(AppDbContext context, string cardSerialNumber) {
Card? card = context.Cards.SingleOrDefault(c => c.SerialNumber == cardSerialNumber);
if (card == null) {
return TransactionRequestStates.CardNotFound.AsTask();
}
if (card.Locked) {
return TransactionRequestStates.CardLocked.AsTask();
}
// Quite a few more checks are done in the real code. If we pass them all, return the card...
return card.AsTask();
}
However, this is all sync code. I want the database call to be async. Following the answer I got from Guru Stron in a previous question, I know I can do something like this...
static EitherAsync<string, int> Square2(int n) =>
TryAsync(async () =>
{
await Task.Delay(300);
throw new Exception();
return n * n;
})
.ToEither(error => error.Message);
However, I don't want to return a string in case of exception, I want to return an enum value, as shown above.
I can't work out how to do this. If I try returning the enum value directly...
private static EitherAsync<TransactionRequestStates, Card> GetCard2(AppDbContext context, string cardSerialNumber) =>
TryAsync(async () => {
Card? card = await context.Cards.SingleOrDefaultAsync(c => c.SerialNumber == cardSerialNumber);
if (card == null) {
return TransactionRequestStates.CardNotFound.AsTask();
}
if (card.Locked) {
return TransactionRequestStates.CardLocked.AsTask();
}
return card;
});
...then I get a compiler error "The type arguments for method 'Prelude.TryAsync(Func<Task>)' cannot be inferred from the usage. Try specifying the type arguments explicitly". My experience with errors of this sort imply that my code is wrong, as if it were correct, the compiler should be able to infer the types.
I tried returning Left<TransactionRequestStates, Card>(TransactionRequestStates.CardNotFound) (with and without calling AsTask() at the end) instead, but that didn't change the error. I also tried adding the call to ToEither() at the end, but that also didn't help.
Anyone able to explain what I need to do to be able to return an EitherAsync<TransactionRequestStates, Card>? Thanks