sbt-avrohugger icon indicating copy to clipboard operation
sbt-avrohugger copied to clipboard

Any way to extend a trait from a record type's companion object ?

Open barambani opened this issue 6 years ago • 2 comments

At the moment when generating a record (I use a SpecificRecord but I guess is the same for the others) also the companion object of the case class is (correctly) generated. So for something like

protocol TestProtocol {

  record Test {
    long id;
    string text;
  }
}

the generated code is

import scala.annotation.switch

case class Test(var id: Long, var text: String) extends org.apache.avro.specific.SpecificRecordBase {
  def this() = this(0L, "")
  def get(field$: Int): AnyRef = {
    (field$: @switch) match {
      case 0 => {
        id
      }.asInstanceOf[AnyRef]
      case 1 => {
        text
      }.asInstanceOf[AnyRef]
      case _ => new org.apache.avro.AvroRuntimeException("Bad index")
    }
  }
  def put(field$: Int, value: Any): Unit = {
    (field$: @switch) match {
      case 0 => this.id = {
        value
      }.asInstanceOf[Long]
      case 1 => this.text = {
        value.toString
      }.asInstanceOf[String]
      case _ => new org.apache.avro.AvroRuntimeException("Bad index")
    }
    ()
  }
  def getSchema: org.apache.avro.Schema = Test.SCHEMA$
}

object Test {
  val SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"Test\",\"namespace\":\"com.hbc.product.streams.topology.source\",\"fields\":[{\"name\":\"id\",\"type\":\"long\"},{\"name\":\"text\",\"type\":\"string\"}]}")
}

Is there any way I could specify a trait that would be extended by the companion object ? Something like

object Test extends TestInstances {
  final val SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"Test\",\"namespace\":\"com.hbc.product.streams.topology.source\",\"fields\":[{\"name\":\"id\",\"type\":\"long\"},{\"name\":\"text\",\"type\":\"string\"}]}")
}

The use case I have is that this way it would be possible to put type class instances in the implicit scope for the generated types avoiding orphans. Thanks.

barambani avatar Sep 05 '18 20:09 barambani

Nothing like exactly like that currently.

However there's a way to customize how case classes are generated. It uses these models. Maybe there's a way to adapt that to get what you want? Maybe something like an optional field with a default of None, or maybe with varargs?

Did you have something in mind, maybe like SpecificRecord.defaultTypes.copy(record = ScalaCaseClass("TestInstances")? Or maybe use a map Map to be more precise, SpecificRecord.defaultTypes.copy(record = ScalaCaseClass(Map("Test" -> "TestInstances"))?

julianpeeters avatar Sep 06 '18 01:09 julianpeeters

The issue I have here is that, when I define a type in idl I would need also to place the typeclass instances for that type in the companion object, so what would help is a way to tell the generator how to place those instances in there. As an example consider I wanted to put the instance for Show[Test] in the implicit scope. I could place it in TestInstances as here

trait TestInstances {
  implicit final val testShow: Show[Test] = ???
}

Or even create a hierarchy that would give me different priorities like

trait TestInstances extends LowPriorityTestInstances {
  implicit final val testShow: Show[Test] = ???
}
sealed trait LowPriorityTestInstances {
  /** other low priority instances here **/
}

Anything that works on the way the case class is generated or on the type I'm afraid doesn't work. May be an option could be to exploit the annotations that are already used for language specific features (like namespaces, ordering etc.). Something like

protocol TestProtocol {

  @companion-is-a("my.fully.qualified.namespace.TestInstances") or
  @companion-extends("my.fully.qualified.namespace.TestInstances")
  record Test {
    long id;
    string text;
  }
}

with the care of making SCHEMA$ final in this case and considering that TestInstances will be part of the manually generated code (so it will be there when the compilation is attempted or it will generate a compilation error). Anyway I will try again with the existing configuration options as you suggest to see if I find a way. Thanks a lot.

barambani avatar Sep 06 '18 09:09 barambani