4

I'm using Scrooge to generate classes. They look something like this, here's an example:

object Flags extends ThriftStructCodec3[Flags] {
  private val NoPassthroughFields = immutable$Map.empty[Short, TFieldBlob]
  val Struct = new TStruct("Flags")
  val IsDangerousField = new TField("isDangerous", TType.BOOL, 1)
  val IsDangerousFieldManifest = implicitly[Manifest[Boolean]]
  val IsWildField = new TField("isWild", TType.BOOL, 2)
  val IsWildFieldManifest = implicitly[Manifest[Boolean]]

  def apply(
    isDangerous: Option[Boolean] = None,
    isWild: Option[Boolean] = None
  ): Flags =
    new Immutable(
      isDangerous,
      isWild
    )

  def unapply(_item: Flags): Option[scala.Product2[Option[Boolean], Option[Boolean]]] = Some(_item)

  object Immutable extends ThriftStructCodec3[Flags] {
    override def encode(_item: Flags, _oproto: TProtocol) { _item.write(_oproto) }
    override def decode(_iprot: TProtocol): Flags = Flags.decode(_iprot)
  }

  /**
   * The default read-only implementation of Flags.  You typically should not need to
   * directly reference this class; instead, use the Flags.apply method to construct
   * new instances.
   */
  class Immutable(
    val isDangerous: Option[Boolean],
    val isWild: Option[Boolean],
    override val _passthroughFields: immutable$Map[Short, TFieldBlob]
  ) extends Flags {
    def this(
      isDangerous: Option[Boolean] = None,
      isWild: Option[Boolean] = None
    ) = this(
      isDangerous,
      isWild,
      Map.empty
    )
  }
}

trait Flags
  extends ThriftStruct
  with scala.Product2[Option[Boolean], Option[Boolean]]
  with java.io.Serializable
{
  import Flags._

  def isDangerous: Option[Boolean]
  def isWild: Option[Boolean]

  def _passthroughFields: immutable$Map[Short, TFieldBlob] = immutable$Map.empty

  def _1 = isDangerous
  def _2 = isWild

  override def productArity: Int = 2

  override def productElement(n: Int): Any = n match {
    case 0 => this.isDangerous
    case 1 => this.isWild
    case _ => throw new IndexOutOfBoundsException(n.toString)
  }

  override def productPrefix: String = "Flags"
}

The aim is to generate from these classes a nested map using Shapeless, do some operation on it and then convert the map back to thrift generated classes.

Using Converting nested case classes to nested Maps using Shapeless and Converting Map[String,Any] to a case class using Shapeless I managed to put together a project which works fine with basic case classes. On top of the examples provided in the stack overflow responses I have also implemented implicits for Option[A].

Now, because Scrooge is not generating proper case classes I was trying to use the Immutable class instead (e.g. Flags.Immutable). Doing this means I have to define implicits for all the thrift classes to convert them to NameOfClass.Immutable. So far so good.

But I have 2 things I seem to not get right (the full implementation can be found in the ClassToMap.scala file):

(1) When generating the map from thrift classes the Option implicit seems to be ignored.

implicit def hconsToMapRecOption[K <: Symbol, V, R <: HList, T <: HList]
    (implicit
     wit: Witness.Aux[K],
     gen: LabelledGeneric.Aux[V, R],
     tmrT: Lazy[ToMapRec[T]],
     tmrH: Lazy[ToMapRec[R]]
    ): ToMapRec[FieldType[K, Option[V]] :: T] = new ToMapRec[FieldType[K, Option[V]] :: T] {
      override def apply(l: FieldType[K, Option[V]] :: T): Map[String, Any] = {
        tmrT.value(l.tail) + (wit.value.name -> l.head.map(value => tmrH.value(gen.to(value))))
      }
    }

(2) When dealing with thrift unions my implicit is not being used.

implicit def hconsToMapRecGoatData[K <: Symbol, R <: HList, T <: HList]
    (implicit
     wit: Witness.Aux[K],
     gen: LabelledGeneric.Aux[MyGoat, R],
     tmrT: Lazy[ToMapRec[T]],
     tmrH: Lazy[ToMapRec[R]]
    ): ToMapRec[FieldType[K, MyGoatData] :: T] = new ToMapRec[FieldType[K, MyGoatData] :: T] {
      override def apply(l: FieldType[K, MyGoatData] :: T): Map[String, Any] = {
        tmrT.value(l.tail) + (wit.value.name -> tmrH.value(gen.to(l.head.goat)))
      }
    }

I haven't spent so much time looking at the map to class implementation, but I assume it should mirror the class to map one.

Community
  • 1
  • 1
Maria Livia
  • 75
  • 1
  • 9
  • 1
    I can't take a closer look at the moment, but are you actually getting the `LabelledGeneric` instances for the Scrooge-generated types? – Travis Brown Feb 01 '17 at 17:10
  • Looking at the `hconsToMapRecOption` now I realise I need to somehow define the `LabelledGeneric` for Scrooge generated types. That's why it works for case classes. Am I right in thinking this is the issue? But I'm not sure how to fix it. – Maria Livia Feb 01 '17 at 18:03
  • I think it most likely is the issue. See [here](https://github.com/milessabin/shapeless/issues/371#issuecomment-158331777). – Miles Sabin Feb 01 '17 at 18:17
  • 1
    I've got a [small example project](https://github.com/travisbrown/scrooge-circe-demo) with some of the necessary machinery, but it's not up-to-date. – Travis Brown Feb 01 '17 at 20:02
  • Thank you, both, this has been really helpful. I now added the required implicits and it works fine. The only thing left is adding support for unions. I'll give it a try later, although at the moment I'm not sure how this will work. – Maria Livia Feb 02 '17 at 11:16

0 Answers0