private def toAvroInternal()

in scio-google-cloud-platform/src/main/scala/com/spotify/scio/bigquery/types/ConverterProvider.scala [163:281]


  private def toAvroInternal(c: blackbox.Context)(tpe: c.Type): c.Tree = {
    import c.universe._

    // =======================================================================
    // Converter helpers
    // =======================================================================

    def cast(fieldName: String, tree: Tree, tpe: Type): Tree = {
      val provider: OverrideTypeProvider =
        OverrideTypeProviderFinder.getProvider
      tpe match {
        case t if provider.shouldOverrideType(c)(t) => q"$tree.toString"
        case t if t =:= typeOf[Boolean]             => tree
        case t if t =:= typeOf[Int]                 => q"$tree.toLong"
        case t if t =:= typeOf[Long]                => tree
        case t if t =:= typeOf[Float]               => q"$tree.toDouble"
        case t if t =:= typeOf[Double]              => tree
        case t if t =:= typeOf[String]              => tree

        case t if t =:= typeOf[BigDecimal] =>
          q"_root_.com.spotify.scio.bigquery.Numeric.bytes($tree)"
        case t if t =:= typeOf[ByteString] =>
          q"_root_.java.nio.ByteBuffer.wrap($tree.toByteArray)"
        case t if t =:= typeOf[Array[Byte]] =>
          q"_root_.java.nio.ByteBuffer.wrap($tree)"

        case t if t =:= typeOf[Instant] =>
          q"_root_.com.spotify.scio.bigquery.Timestamp.micros($tree)"
        case t if t =:= typeOf[LocalDate] =>
          q"_root_.com.spotify.scio.bigquery.Date.days($tree)"
        case t if t =:= typeOf[LocalTime] =>
          q"_root_.com.spotify.scio.bigquery.Time.micros($tree)"
        case t if t =:= typeOf[LocalDateTime] =>
          // LocalDateTime is read as avro string
          // on write we should use `local-timestamp-micros`
          q"_root_.com.spotify.scio.bigquery.DateTime.format($tree)"

        // different than nested record match below, even though thore are case classes
        case t if t =:= typeOf[Geography] =>
          q"$tree.wkt"
        case t if t =:= typeOf[Json] =>
          q"$tree.wkt"
        case t if t =:= typeOf[BigNumeric] =>
          q"_root_.com.spotify.scio.bigquery.types.BigNumeric.bytes($tree)"

        // nested records
        case t if isCaseClass(c)(t) =>
          val fn = TermName("r" + s"${fieldName}_${t.typeSymbol.name}")
          q"""{
                val $fn = $tree
                ${constructor(fieldName, t, fn)}
              }
          """
        case _ => c.abort(c.enclosingPosition, s"Unsupported type: $tpe")
      }
    }

    def option(fieldName: String, tree: Tree, tpe: Type): Tree =
      q"if ($tree.isDefined) ${cast(fieldName, q"$tree.get", tpe)} else null"

    def list(fieldName: String, tree: Tree, tpe: Type): Tree =
      q"asJava($tree.map(x => ${cast(fieldName, q"x", tpe)}))"

    def field(symbol: Symbol, fn: TermName): (String, Tree) = {
      val name = symbol.name.toString
      val tpe = symbol.asMethod.returnType

      val tree = q"$fn.${TermName(name)}"
      if (tpe.erasure =:= typeOf[Option[_]].erasure) {
        (name, option(name, tree, tpe.typeArgs.head))
      } else if (tpe.erasure =:= typeOf[List[_]].erasure) {
        (name, list(name, tree, tpe.typeArgs.head))
      } else {
        (name, cast(name, tree, tpe))
      }
    }

    def constructor(fieldName: String, tpe: Type, fn: TermName): Tree = {
      val sets = tpe.erasure match {
        case t if isCaseClass(c)(t) => getFields(c)(t).map(s => field(s, fn))
        case _                      => c.abort(c.enclosingPosition, s"Unsupported type: $tpe")
      }

      val header = {
        q"""
            // Schema name must match fieldName, rather than nested case class name
            val result = {
              import _root_.scala.jdk.CollectionConverters._
              val recordSchema = ${p(c, SType)}.avroSchemaOf[$tpe]
              new _root_.org.apache.avro.generic.GenericRecordBuilder(
                _root_.org.apache.avro.Schema.createRecord(
                  $fieldName,
                  recordSchema.getDoc,
                  recordSchema.getNamespace,
                  recordSchema.isError,
                  recordSchema.getFields.asScala.map(f => new _root_.org.apache.avro.Schema.Field(f.name(), f.schema(), f.doc(), f.defaultVal())).asJava
                )
              )
            }
        """
      }
      val body = sets.map { case (name, value) =>
        q"result.set($name, $value)"
      }
      val footer = q"result.build()"
      q"{$header; ..$body; $footer}"
    }

    // =======================================================================
    // Entry point
    // =======================================================================

    val tn = TermName("r")
    q"""(r: $tpe) => {
          import _root_.scala.jdk.javaapi.CollectionConverters._
          ${constructor(tpe.typeSymbol.name.toString, tpe, tn)}
        }
    """
  }