scaladoobie

Scala compilation fails with: Could not find implicit value for parameter W


I got a doobie query that doesn't want to compile:

package de.x.vmdbplanningvalues.impl.queries

import de.x.campaignplans.CampaignPlanId
import de.x.distributionbrands.DistributionBrandId
import de.x.vmdbplanningvalues._
import doobie._

object UpdateVmdbPlanningValuesQuery {
  case class VmdbPlanningUpdate(
      creditRatingRejections: Option[Double],
      goodsCoupons: Option[Double],
      customerDiscounts: Option[Double],
      campaignPlanId: String,
      distributionBrandId: String,
      country: String,
      categoryId: String,
      lane: VmdbLane
  )

  def apply(
      distributionBrand: DistributionBrandId,
      country: String,
      campaignPlanId: CampaignPlanId,
      category: String,
      updates: List[VmdbPlanningValuesForVmdbLane]
  ): ConnectionIO[Unit] = {
    for {
      _ <- updateQuery(updates.map(update =>
        VmdbPlanningUpdate(
          update.creditRatingRejections,
          update.goodsCoupons,
          update.customerDiscounts,
          campaignPlanId.id,
          distributionBrand.id,
          country,
          category,
          update.lane
        )
      ))
    } yield ()
  }

  def updateQuery[T: Write](updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = {
    val sql =
      """
         UPDATE vmdb_planning_values as vmdb
         SET vmdb.credit_rating_rejections = ?,
             vmdb.goods_coupons = ?,
             vmdb.customer_discounts = ?
         FROM campaign_plan cp
         WHERE cp.id = ?
            AND vmdb.distribution_brand_id = ?
            AND vmdb.country_id = ?
            AND vmdb.year=DATE_PART('year', cp.start_date)
            AND vmdb.quarter=DATE_PART('quarter', cp.start_date)
            AND vmdb.category_id = ?
            AND vmdb.lane = ?
      """

    Update[VmdbPlanningUpdate](sql).updateMany(updates)
  }
}

However it fails with the following error:

[error] /Users/johannesklauss/Documents/campaign-service/server/src/main/scala/de/x/vmdbplanningvalues/impl/queries/UpdateVmdbPlanningValuesQuery.scala:60:35: could not find implicit value for parameter W: doobie.Write[de.x.vmdbplanningvalues.impl.queries.UpdateVmdbPlanningValuesQuery.VmdbPlanningUpdate]
[error]     Update[VmdbPlanningUpdate](sql).updateMany(updates)
[error] 

I am not quite sure what the error message means, since I am still a bit fuzzy about implicits in Scala. Does anybody have an idea?

Edit: Added VmdbLane.scala:

package de.x.vmdbplanningvalues

import io.circe.Decoder.Result
import io.circe._

sealed trait VmdbLane {
  override def toString: String = VmdbLane.toEnum(this)
}

object VmdbLane {

  case object New extends VmdbLane
  case object CarryOver extends VmdbLane
  case object Sale extends VmdbLane
  case object Sum extends VmdbLane

  def toEnum(e: VmdbLane): String =
    e match {
      case New => "new"
      case CarryOver => "carryOver"
      case Sale => "sale"
      case Sum => "sum"
    }

  def fromEnum(s: String): Option[VmdbLane] =
    Option(s) collect {
      case "new" => New
      case "carryOver" => CarryOver
      case "sale" => Sale
      case "sum" => Sum
    }

  implicit val jsonFormat: Encoder[VmdbLane] with Decoder[VmdbLane] =
    new Encoder[VmdbLane] with Decoder[VmdbLane] {
      override def apply(a: VmdbLane): Json = Encoder.encodeString(toEnum(a))
      override def apply(c: HCursor): Result[VmdbLane] =
        c.value.asString.flatMap(s => fromEnum(s)) match {
          case Some(a) => Right(a)
          case None => Left(DecodingFailure("VmdbLane", c.history))
        }
    }
}


Solution

  • The problem was not related to Circe it is related to how the custom doobie type mapping works. If you include something like inside the VmdbLane object:

    implicit val natGet: Get[VmdbLane] = Get[String].map(in => {
        in match {
          case "New"       => New
          case "CarryOver" => CarryOver
          case "Sale"      => Sale
          case "Sum"       => Sum
        }
      })
    
    implicit val natPut: Put[VmdbLane] = Put[String].contramap {
        case New       => "New"
        case CarryOver => "CarryOver"
        case Sale      => "Sale"
        case Sum       => "Sum"
      }
    

    The compiler should include the mapping an it should work.