From f55222fba91b43b0b8a9e02b55d7b4c6743b09d1 Mon Sep 17 00:00:00 2001 From: Roman-Statsura Date: Mon, 17 Jun 2024 17:21:24 +0400 Subject: [PATCH] Add vulcan avro integration --- .github/workflows/ci.yml | 2 +- build.sbt | 10 +++++ .../main/scala/derevo/vulcan/avroCodec.scala | 18 +++++++++ .../scala/derevo/vulcan/AvroCodecTest.scala | 37 +++++++++++++++++++ project/Dependencies.scala | 4 ++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 modules/vulcan/src/main/scala/derevo/vulcan/avroCodec.scala create mode 100644 modules/vulcan/src/test/scala/derevo/vulcan/AvroCodecTest.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 377734ff..16d65206 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: run: sbt ++${{ matrix.scala }} test - name: Compress target directories - run: tar cf targets.tar modules/tests/target modules/tethys/target modules/pureconfig/target modules/catsTagless/target modules/ciris/target modules/scalacheck/target modules/circeMagnolia/target modules/circe/target target modules/tethysMagnolia/target modules/cats/target modules/core/target modules/sangria/target modules/reactivemongo/target project/target + run: tar cf targets.tar modules/tests/target modules/tethys/target modules/pureconfig/target modules/catsTagless/target modules/ciris/target modules/scalacheck/target modules/circeMagnolia/target modules/circe/target target modules/tethysMagnolia/target modules/cats/target modules/core/target modules/vulcan/target modules/sangria/target modules/reactivemongo/target project/target - name: Upload target directories uses: actions/upload-artifact@v2 diff --git a/build.sbt b/build.sbt index 57529529..921e0e2d 100644 --- a/build.sbt +++ b/build.sbt @@ -51,6 +51,7 @@ lazy val derevo = project tethys, tethysMagnolia, sangria, + vulcan, tests, ) @@ -164,6 +165,15 @@ lazy val sangria = ) .dependsOn(core) +lazy val vulcan = + (project in file("modules/vulcan")) + .settings(publishSettings) + .settings( + name := "derevo-vulcan", + libraryDependencies ++= Seq(Dependencies.vulcanGeneric), + ) + .dependsOn(core) + lazy val tests = (project in file("modules/tests")) .settings(noPublishSettings) diff --git a/modules/vulcan/src/main/scala/derevo/vulcan/avroCodec.scala b/modules/vulcan/src/main/scala/derevo/vulcan/avroCodec.scala new file mode 100644 index 00000000..76b144ae --- /dev/null +++ b/modules/vulcan/src/main/scala/derevo/vulcan/avroCodec.scala @@ -0,0 +1,18 @@ +package derevo.vulcan + +import derevo.{Derivation, NewTypeDerivation} +import magnolia.{CaseClass, Magnolia, SealedTrait} +import vulcan.Codec +import vulcan.generic._ + +object avroCodec extends Derivation[Codec] with NewTypeDerivation[Codec] { + + type Typeclass[A] = Codec[A] + + def combine[A](caseClass: CaseClass[Codec, A]): Codec[A] = Codec.combine(caseClass) + + def dispatch[A](sealedTrait: SealedTrait[Codec, A]): Codec.Aux[Any, A] = Codec.dispatch(sealedTrait) + + def instance[A]: Codec[A] = macro Magnolia.gen[A] + +} diff --git a/modules/vulcan/src/test/scala/derevo/vulcan/AvroCodecTest.scala b/modules/vulcan/src/test/scala/derevo/vulcan/AvroCodecTest.scala new file mode 100644 index 00000000..fd535bce --- /dev/null +++ b/modules/vulcan/src/test/scala/derevo/vulcan/AvroCodecTest.scala @@ -0,0 +1,37 @@ +package derevo.vulcan + +import org.apache.avro.Schema +import derevo.derive +import org.scalatest.Assertion +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import vulcan.Codec +import vulcan.generic.{AvroDoc, AvroName} + +import java.time.Instant + +@derive(avroCodec) +case class Foo(i: Int, s: String, bar: Bar) + +@derive(avroCodec) +case class Bar( + @AvroName("time_dttm") @AvroDoc("Timestamp") + timestamp: Instant, + @AvroName("int_list") @AvroDoc("List of ints") + intList: List[Int] +) + +class AvroCodecTest extends AnyFlatSpec with Matchers { + + val fooSchema: String = + """{"type":"record","name":"Foo","namespace":"derevo.vulcan","fields":[{"name":"i","type":"int"},{"name":"s","type":"string"},{"name":"bar","type":{"type":"record","name":"Bar","fields":[{"name":"time_dttm","type":{"type":"long","logicalType":"timestamp-millis"},"doc":"Timestamp"},{"name":"int_list","type":{"type":"array","items":"int"},"doc":"List of ints"}]}}]}""" + + def assertSchema[A: Codec](schemaStr: String): Assertion = { + val expectedSchema = new Schema.Parser().parse(schemaStr.mkString.trim) + Codec[A].schema shouldBe Right(expectedSchema) + } + + "Writer derivation for ADT" should "work correctly" in { + assertSchema[Foo](fooSchema) + } +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ed4fbe7f..b48ab8c2 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -41,6 +41,8 @@ object Dependencies { val macroParadise = "2.1.1" val sangria = "3.2.0" + + val vulcan = "1.10.1" } lazy val magnolia = "com.propensive" %% "magnolia" % Version.magnolia @@ -66,6 +68,8 @@ object Dependencies { lazy val estatico = "io.estatico" %% "newtype" % Version.estatico lazy val supertagged = "org.rudogma" %% "supertagged" % Version.supertagged + lazy val vulcanGeneric = "com.github.fd4s" %% "vulcan-generic" % Version.vulcan + lazy val macroParadise = "org.scalamacros" % "paradise" % Version.macroParadise cross CrossVersion.patch lazy val kindProjector = "org.typelevel" %% "kind-projector" % Version.kindProjector cross CrossVersion.patch