0

I have a list of emails, for each one I'll look it up in email table to see if that email exist. if it does, do nothing else I'll throw error. here is my code;

def lookupEmailStatus(email: EmailAddress, requestId: RequestId)(
      implicit ec: ExecutionContext): HttpServiceResult[List[EmailStatusDTO]] = {
    emailDatabase
      .getEmailStatusByEmail(email, requestId)
      .map(
        l =>
        if (l.isEmpty) {
          logger.error(
            LoggingMessage(
              requestId,
              s"Email status not found by ${email.email} failed"))
          EntityNotFound(s"${email.email}", requestId)
        } else {
          l
        }
      )
      .leftMap[HttpError] {
        case e =>
          logger.error(
            LoggingMessage(
              requestId,
              s"Retrieve email status by ${email.email} failed"))
          DatabaseError(e.message, requestId)
      }
  }

when I ran the code got error:

Error:(57, 27) type mismatch;
 found   : cats.data.EitherT[model.HttpError,Product with Serializable]
 required: model.HttpServiceResult[List[EmailStatusDTO]]
    (which expands to)  cats.data.EitherT[model.HttpError,List[EmailStatusDTO]]
      .leftMap[HttpError] {

if I removed the .map(..) method it'll works fine but that's not what I want:

def lookupEmailStatus(email: EmailAddress, requestId: RequestId)(
          implicit ec: ExecutionContext): HttpServiceResult[List[EmailStatusDTO]] = {
        emailDatabase
          .getEmailStatusByEmail(email, requestId)
          .leftMap[HttpError] {
            case e =>
              logger.error(
                LoggingMessage(
                  requestId,
                  s"Retrieve email status by ${email.email} failed"))
              DatabaseError(e.message, requestId)
          }
      }

here are the type definitions:

type HttpServiceResult[A] = ServiceResult[HttpError, A]
type ServiceResult[Err, A] = EitherT[Future, Err, A]
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
user468587
  • 4,799
  • 24
  • 67
  • 124

2 Answers2

4

If one branch of if returns a list and other returns an error, then totally if returns Product with Serializable.

Try to replace map with flatMap and wrap results of branches with EitherT

def lookupEmailStatus(email: EmailAddress, requestId: RequestId)(
    implicit ec: ExecutionContext): HttpServiceResult[List[EmailStatusDTO]] = 
    emailDatabase
      .getEmailStatusByEmail(email, requestId)
      .leftMap[HttpError] {
        case e =>
          logger.error(
            LoggingMessage(
              requestId,
              s"Retrieve email status by ${email.email} failed"))
          DatabaseError(e.message, requestId)
      }.flatMap[HttpError, List[EmailStatusDTO]](
        /*(*/l/*: List[EmailStatusDTO])*/ =>
          if (l.isEmpty) {
            logger.error(
              LoggingMessage(
                requestId,
                s"Email status not found by ${email.email} failed"))
            EitherT.leftT/*[Future, List[EmailStatusDTO]]*/(EntityNotFound(s"${email.email}", requestId))
          } else {
            EitherT.rightT/*[Future, HttpError]*/(l)
          }
      )
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
1

As @dmytro-mitin already mentioned, the root cause of your issue is that you're not returning the same type in both branches of your conditional. One way to fix that would be to make sure that you're returning the correct type.

A different and, in my opinion, better way would be to use cats.data.EitherT.ensure for the list.isEmpty check. That way you're being explicit about what you care about (i.e. if the list is empty, return an error) and don't have to manually deal with the happy case.

Your code would then become:

def lookupEmailStatus(email: EmailAddress, requestId: RequestId)(
      implicit ec: ExecutionContext): HttpServiceResult[List[EmailStatusDTO]] = {
    emailDatabase
      .getEmailStatusByEmail(email, requestId)
      .ensure({
         logger.error(LoggingMessage(requestId, s"Email status not found by ${email.email} failed"))}
         EntityNotFound(s"${email.email}", requestId)
      })(!_.isEmpty)
      .leftMap[HttpError] {
        case e =>
          logger.error(
            LoggingMessage(
              requestId,
              s"Retrieve email status by ${email.email} failed"))
          DatabaseError(e.message, requestId)
      }
  }
Denis Rosca
  • 3,409
  • 19
  • 38
  • `leftMap` should be before `ensure`. Otherwise `EntityNotFound` will be swallowed by `DatabaseError`. Or you can match in `leftMap`. – Dmytro Mitin Mar 07 '19 at 15:22
  • thanks for the tip, one thing puzzled me is if they have leftmap, why can't they have a rightMap as well so that I can do something like this: emailDatabase.getEmailStatusByEmail(email, reuqestId).leftMap[HttpError]{...logging + DatabaseError...}.rightMap{ l=> if (l.isEmpty) E.left(EntityNotFound)} – user468587 Mar 07 '19 at 16:53