diff --git a/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/records.scala b/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/records.scala index c002e6ae..e5dbdc0d 100644 --- a/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/records.scala +++ b/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/records.scala @@ -11,8 +11,8 @@ object Records: def schema[T](ctx: CaseClass[SchemaFor, T]): SchemaFor[T] = { - val annos = Annotations(ctx.annotations) - val naming = Names(ctx.typeInfo, annos, ctx.typeAnnotations) + val annos = Annotations(ctx) + val naming = Names(ctx.typeInfo, annos) val error = annos.error val record = Schema.createRecord( diff --git a/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/valuetypes.scala b/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/valuetypes.scala index 3d8f94cf..48729387 100644 --- a/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/valuetypes.scala +++ b/avro4s-core/src/main/scala/com/sksamuel/avro4s/schemas/valuetypes.scala @@ -13,7 +13,7 @@ object ValueTypes { */ def schema[T](ctx: CaseClass[SchemaFor, T]): Schema = val annos: Annotations = Annotations(ctx) // taking over @AvroFixed and the like - val names = Names(ctx.typeInfo, annos, ctx.typeAnnotations) + val names = Names(ctx.typeInfo, annos) // if the class is a value type, then we need to use the schema for the single field inside the type // in other words, if we have `case class Foo(str: String) extends AnyVal` then this acts just like String. diff --git a/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Annotations.scala b/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Annotations.scala index 83202f6f..9aed98db 100644 --- a/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Annotations.scala +++ b/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Annotations.scala @@ -3,13 +3,14 @@ package com.sksamuel.avro4s.typeutils import com.sksamuel.avro4s.{AvroAliasable, AvroDoc, AvroDocumentable, AvroErasedName, AvroError, AvroFixed, AvroName, AvroNameable, AvroNamespace, AvroNoDefault, AvroProp, AvroProperty, AvroSortPriority, AvroTransient, AvroUnionPosition} import magnolia1.{CaseClass, TypeInfo} -class Annotations(annos: Seq[Any]) { +class Annotations(annos: Seq[Any], inheritedAnnos: Seq[Any] = Nil) { + private[this] val allAnnos: Seq[Any] = annos ++ inheritedAnnos def name: Option[String] = annos.collectFirst { case t: AvroNameable => t.name } - def namespace: Option[String] = annos.collectFirst { + def namespace: Option[String] = allAnnos.collectFirst { case t: AvroNamespace => t.namespace } @@ -60,6 +61,6 @@ class Annotations(annos: Seq[Any]) { } object Annotations { - def apply(ctx: CaseClass[_, _]): Annotations = new Annotations(ctx.annotations) - def apply(annos: Seq[Any]): Annotations = new Annotations(annos) + def apply(ctx: CaseClass[_, _]): Annotations = new Annotations(ctx.annotations, ctx.inheritedAnnotations) + def apply(annos: Seq[Any]): Annotations = new Annotations(annos, Nil) } \ No newline at end of file diff --git a/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Names.scala b/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Names.scala index f68e5d86..46ff5730 100644 --- a/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Names.scala +++ b/avro4s-core/src/main/scala/com/sksamuel/avro4s/typeutils/Names.scala @@ -6,7 +6,7 @@ import magnolia1.TypeInfo * Extracts names and namespaces from a type. * Takes into consideration provided annotations. */ -case class Names(typeInfo: TypeInfo, val annos: Annotations, val typeAnnos: Seq[Any] = Nil) { +case class Names(typeInfo: TypeInfo, val annos: Annotations) { private val defaultNamespace = typeInfo.owner.replaceAll("\\.", "").stripSuffix(".package") @@ -62,7 +62,7 @@ case class Names(typeInfo: TypeInfo, val annos: Annotations, val typeAnnos: Seq[ } object Names { - def apply(info: TypeInfo): Names = Names(info, Annotations(Nil), Nil) + def apply(info: TypeInfo): Names = Names(info, Annotations(Nil)) // def apply[F[_], T](subtype: Subtype[F, T]): NameExtractor = NameExtractor(subtype.typeName, subtype.annotations) // // def apply(typeName: TypeName, annos: Seq[Any]): NameExtractor = NameExtractor(TypeInfo(typeName, annos)) diff --git a/avro4s-core/src/test/scala/com/sksamuel/avro4s/schema/NamespaceSchemaTest.scala b/avro4s-core/src/test/scala/com/sksamuel/avro4s/schema/NamespaceSchemaTest.scala index 24a21e8f..77f8df44 100644 --- a/avro4s-core/src/test/scala/com/sksamuel/avro4s/schema/NamespaceSchemaTest.scala +++ b/avro4s-core/src/test/scala/com/sksamuel/avro4s/schema/NamespaceSchemaTest.scala @@ -3,6 +3,8 @@ package com.sksamuel.avro4s.schema import com.sksamuel.avro4s.AvroSchema import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import com.sksamuel.avro4s.AvroNamespace +import com.sksamuel.avro4s.AvroName class NamespaceSchemaTest extends AnyFunSuite with Matchers { @@ -30,6 +32,16 @@ class NamespaceSchemaTest extends AnyFunSuite with Matchers { val schema = AvroSchema[NamespaceTestFoo] schema.toString(true) shouldBe expected.toString(true) } + test("case classes should inherit namespace from parent sealed trait") { + @AvroNamespace("foobar") + @AvroName("Qux") + sealed trait Foo + object Foo { + case class Bla() extends Foo + } + AvroSchema[Foo.Bla].getNamespace() shouldBe "foobar" + AvroSchema[Foo.Bla].getName() shouldBe "Bla" + } }