From 5ceece9e39e1bcbee971fa04860e34f0cb8c9de9 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:37:30 +0200 Subject: [PATCH] Handle non-empty schemas --- build.sbt | 3 +- project/BuildHelper.scala | 2 +- .../zio/schema/codec/AvroSchemaCodec.scala | 28 +++++++++++++++---- .../schema/codec/AvroSchemaCodecSpec.scala | 5 +--- .../schema/codec/BsonSchemaCodecSpec.scala | 4 +-- .../main/scala/zio/schema/CachedDeriver.scala | 12 +++++--- .../scala/zio/schema/codec/JsonCodec.scala | 10 +++---- .../zio/schema/codec/ProtobufCodec.scala | 4 +-- .../src/main/scala/zio/schema/Differ.scala | 3 +- 9 files changed, 43 insertions(+), 28 deletions(-) diff --git a/build.sbt b/build.sbt index 7c77d3458..478857687 100644 --- a/build.sbt +++ b/build.sbt @@ -39,8 +39,7 @@ inThisBuild( ThisBuild / publishTo := sonatypePublishToBundle.value scalacOptions ++= Seq("-scalajs") -addCommandAlias("prepare", "fix; fmt") -addCommandAlias("fmt", "all scalafmtSbt scalafmtAll") +addCommandAlias("fmt", "all scalafmtSbt scalafmtAll;fix") addCommandAlias("fmtCheck", "all scalafmtSbtCheck scalafmtCheckAll") addCommandAlias("fix", "scalafixAll") addCommandAlias("fixCheck", "scalafixAll --check") diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index d7ff860ab..eda8731f6 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -188,7 +188,7 @@ object BuildHelper { baseDirectory.value ) }, - nativeConfig ~= { _.withMultithreading(false) }, + nativeConfig ~= { _.withMultithreading(false) } ) def buildInfoSettings(packageName: String) = Seq( diff --git a/zio-schema-avro/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala b/zio-schema-avro/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala index 95cf24b4c..7652f9d33 100644 --- a/zio-schema-avro/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala +++ b/zio-schema-avro/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala @@ -277,12 +277,14 @@ object AvroSchemaCodec extends AvroSchemaCodec { private def toAvroSchema(schema: Schema[_]): scala.util.Either[String, SchemaAvro] = { schema match { - case e: Enum[_] => toAvroEnum(e) - case record: Record[_] => toAvroRecord(record) - case map: Schema.Map[_, _] => toAvroMap(map) - case seq: Schema.Sequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray) - case set: Schema.Set[_] => toAvroSchema(set.elementSchema).map(SchemaAvro.createArray) - case Transform(codec, _, _, _, _) => toAvroSchema(codec) + case e: Enum[_] => toAvroEnum(e) + case record: Record[_] => toAvroRecord(record) + case map: Schema.Map[_, _] => toAvroMap(map) + case map: Schema.NonEmptyMap[_, _] => toAvroMap(map) + case seq: Schema.Sequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray) + case seq: Schema.NonEmptySequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray) + case set: Schema.Set[_] => toAvroSchema(set.elementSchema).map(SchemaAvro.createArray) + case Transform(codec, _, _, _, _) => toAvroSchema(codec) case Primitive(standardType, _) => standardType match { case StandardType.UnitType => Right(SchemaAvro.create(SchemaAvro.Type.NULL)) @@ -624,6 +626,18 @@ object AvroSchemaCodec extends AvroSchemaCodec { toAvroSchema(tupleSchema).map(SchemaAvro.createArray) } + private[codec] def toAvroMap(map: NonEmptyMap[_, _]): scala.util.Either[String, SchemaAvro] = + map.keySchema match { + case p: Schema.Primitive[_] if p.standardType == StandardType.StringType => + toAvroSchema(map.valueSchema).map(SchemaAvro.createMap) + case _ => + val tupleSchema = Schema + .Tuple2(map.keySchema, map.valueSchema) + .annotate(AvroAnnotations.name("Tuple")) + .annotate(AvroAnnotations.namespace("scala")) + toAvroSchema(tupleSchema).map(SchemaAvro.createArray) + } + private[codec] def toAvroDecimal(schema: Schema[_]): scala.util.Either[String, SchemaAvro] = { val scale = schema.annotations.collectFirst { case AvroAnnotations.scale(s) => s } .getOrElse(AvroAnnotations.scale().scale) @@ -820,7 +834,9 @@ object AvroSchemaCodec extends AvroSchemaCodec { case c: Dynamic => Right(c) case c: GenericRecord => Right(c) case c: Map[_, _] => Right(c) + case c: NonEmptyMap[_, _] => Right(c) case c: Sequence[_, _, _] => Right(c) + case c: NonEmptySequence[_, _, _] => Right(c) case c: Set[_] => Right(c) case c: Fail[_] => Right(c) case c: Lazy[_] => Right(c) diff --git a/zio-schema-avro/src/test/scala/zio/schema/codec/AvroSchemaCodecSpec.scala b/zio-schema-avro/src/test/scala/zio/schema/codec/AvroSchemaCodecSpec.scala index bc19b6b82..7306bbce1 100644 --- a/zio-schema-avro/src/test/scala/zio/schema/codec/AvroSchemaCodecSpec.scala +++ b/zio-schema-avro/src/test/scala/zio/schema/codec/AvroSchemaCodecSpec.scala @@ -1918,10 +1918,7 @@ object AssertionHelper { def recordFields(assertion: Assertion[Iterable[Schema.Field[_, _]]]): Assertion[Schema.Record[_]] = Assertion.assertionRec[Schema.Record[_], Chunk[Field[_, _]]]("hasRecordField")( assertion - ) { - case r: Schema.Record[_] => Some(r.fields) - case _ => None - } + )((r: Schema.Record[_]) => Some(r.fields)) def hasSequenceElementSchema[A](assertion: Assertion[Schema[A]]): Assertion[Schema.Sequence[_, A, _]] = Assertion.hasField("schemaA", _.elementSchema, assertion) diff --git a/zio-schema-bson/src/test/scala/zio/schema/codec/BsonSchemaCodecSpec.scala b/zio-schema-bson/src/test/scala/zio/schema/codec/BsonSchemaCodecSpec.scala index 941dc1450..e3c743c50 100644 --- a/zio-schema-bson/src/test/scala/zio/schema/codec/BsonSchemaCodecSpec.scala +++ b/zio-schema-bson/src/test/scala/zio/schema/codec/BsonSchemaCodecSpec.scala @@ -40,10 +40,10 @@ object BsonSchemaCodecSpec extends ZIOSpecDefault { implicit lazy val schema: Schema[Tree] = DeriveSchema.gen implicit lazy val codec: BsonCodec[Tree] = BsonSchemaCodec.bsonCodec(schema) - private val genLeaf = Gen.int.map(Leaf) + private val genLeaf = Gen.int.map(Leaf.apply) lazy val gen: Gen[Any, Tree] = Gen.sized { i => - if (i >= 2) Gen.oneOf(genLeaf, Gen.suspend(gen.zipWith(gen)(Branch)).resize(i / 2)) + if (i >= 2) Gen.oneOf(genLeaf, Gen.suspend(gen.zipWith(gen)(Branch.apply)).resize(i / 2)) else genLeaf } } diff --git a/zio-schema-derivation/shared/src/main/scala/zio/schema/CachedDeriver.scala b/zio-schema-derivation/shared/src/main/scala/zio/schema/CachedDeriver.scala index 53d516a65..3dd37ac99 100644 --- a/zio-schema-derivation/shared/src/main/scala/zio/schema/CachedDeriver.scala +++ b/zio-schema-derivation/shared/src/main/scala/zio/schema/CachedDeriver.scala @@ -126,18 +126,22 @@ private[schema] object CachedDeriver { final case class Tuple2[A, B](leftKey: CacheKey[A], rightKey: CacheKey[B]) extends CacheKey[(A, B)] final case class Set[A](element: CacheKey[A]) extends CacheKey[Set[A]] final case class Map[K, V](key: CacheKey[K], valuew: CacheKey[V]) extends CacheKey[Map[K, V]] + final case class NonEmptyMap[K, V](key: CacheKey[K], valuew: CacheKey[V]) extends CacheKey[NonEmptyMap[K, V]] final case class Misc[A](schema: Schema[A]) extends CacheKey[A] def fromStandardType[A](st: StandardType[A]): CacheKey[A] = Primitive(st) def fromSchema[A](schema: Schema[A]): CacheKey[A] = schema match { - case e: Schema.Enum[_] => WithId(e.id) - case record: Schema.Record[_] => WithId(record.id) - case seq: Schema.Sequence[_, _, _] => WithIdentityObject(fromSchema(seq.elementSchema), seq.identity) - case set: Schema.Set[_] => Set(fromSchema(set.elementSchema)).asInstanceOf[CacheKey[A]] + case e: Schema.Enum[_] => WithId(e.id) + case record: Schema.Record[_] => WithId(record.id) + case seq: Schema.Sequence[_, _, _] => WithIdentityObject(fromSchema(seq.elementSchema), seq.identity) + case seq: Schema.NonEmptySequence[_, _, _] => WithIdentityObject(fromSchema(seq.elementSchema), seq.identity) + case set: Schema.Set[_] => Set(fromSchema(set.elementSchema)).asInstanceOf[CacheKey[A]] case map: Schema.Map[_, _] => Map(fromSchema(map.keySchema), fromSchema(map.valueSchema)).asInstanceOf[CacheKey[A]] + case map: Schema.NonEmptyMap[_, _] => + Map(fromSchema(map.keySchema), fromSchema(map.valueSchema)).asInstanceOf[CacheKey[A]] case Schema.Transform(inner, _, _, _, identity) => WithIdentityObject(fromSchema(inner), identity) case Schema.Primitive(standardType, _) => fromStandardType(standardType) case optional: Schema.Optional[_] => Optional(fromSchema(optional.schema)).asInstanceOf[CacheKey[A]] diff --git a/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala b/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala index 424c26403..93e9113ac 100644 --- a/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala +++ b/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala @@ -183,11 +183,11 @@ object JsonCodec { //scalafmt: { maxColumn = 400, optIn.configStyleArguments = false } private[codec] def schemaEncoder[A](schema: Schema[A], cfg: Config, discriminatorTuple: DiscriminatorTuple = Chunk.empty): ZJsonEncoder[A] = schema match { - case Schema.Primitive(standardType, _) => primitiveCodec(standardType).encoder - case Schema.Sequence(schema, _, g, _, _) => ZJsonEncoder.chunk(schemaEncoder(schema, cfg, discriminatorTuple)).contramap(g) - case Schema.NonEmptySequence(schema, _, g, _, _) => ZJsonEncoder.chunk(schemaEncoder(schema, cfg, discriminatorTuple)).contramap(g) - case Schema.Map(ks, vs, _) => mapEncoder(ks, vs, discriminatorTuple, cfg) - case Schema.NonEmptyMap(ks, vs, _) => mapEncoder(ks, vs, discriminatorTuple, cfg).contramap[NonEmptyMap[Any, Any]](_.toMap.asInstanceOf[Map[Any, Any]]).asInstanceOf[ZJsonEncoder[A]] + case Schema.Primitive(standardType, _) => primitiveCodec(standardType).encoder + case Schema.Sequence(schema, _, g, _, _) => ZJsonEncoder.chunk(schemaEncoder(schema, cfg, discriminatorTuple)).contramap(g) + case Schema.NonEmptySequence(schema, _, g, _, _) => ZJsonEncoder.chunk(schemaEncoder(schema, cfg, discriminatorTuple)).contramap(g) + case Schema.Map(ks, vs, _) => mapEncoder(ks, vs, discriminatorTuple, cfg) + case Schema.NonEmptyMap(ks: Schema[kt], vs: Schema[vt], _) => mapEncoder(ks, vs, discriminatorTuple, cfg).contramap[NonEmptyMap[kt, vt]](_.toMap.asInstanceOf[Map[kt, vt]]).asInstanceOf[ZJsonEncoder[A]] case Schema.Set(s, _) => ZJsonEncoder.chunk(schemaEncoder(s, cfg, discriminatorTuple)).contramap(m => Chunk.fromIterable(m)) case Schema.Transform(c, _, g, a, _) => transformEncoder(a.foldLeft(c)((s, a) => s.annotate(a)), g, cfg) diff --git a/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala b/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala index 1213c0498..c51bafeef 100644 --- a/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala +++ b/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala @@ -182,8 +182,6 @@ object ProtobufCodec { encodeKey(rightWireType, Some(2)) ++ rightDecoder.remainder encodeKey(WireType.LengthDelimited(data.size), Some(seqIndex)) ++ data - case other => - throw new IllegalStateException(s"Invalid state in processDictionary: $other") } }.flatten val data = encodeKey( @@ -371,7 +369,7 @@ object ProtobufCodec { byteBuffer.order(ByteOrder.LITTLE_ENDIAN) byteBuffer.putDouble(v) encodeKey(WireType.Bit64, fieldNumber) ++ Chunk.fromArray(byteBuffer.array) - case (StandardType.BinaryType, bytes: Chunk[Byte]) => + case (StandardType.BinaryType, bytes: Chunk[Byte] @unchecked) => encodeKey(WireType.LengthDelimited(bytes.length), fieldNumber) ++ bytes case (StandardType.CharType, c: Char) => encodePrimitive(fieldNumber, StandardType.StringType, c.toString) diff --git a/zio-schema/shared/src/main/scala/zio/schema/Differ.scala b/zio-schema/shared/src/main/scala/zio/schema/Differ.scala index 2f1e22a19..132a01832 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Differ.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Differ.scala @@ -25,6 +25,7 @@ import java.util.{ Currency, UUID } import scala.annotation.nowarn import scala.collection.immutable.ListMap +import zio.prelude.NonEmptyMap import zio.schema.diff.Edit import zio.{ Chunk, ChunkBuilder } @@ -269,7 +270,7 @@ object Differ { case s @ Schema.NonEmptySequence(schema, _, f, _, _) => fromSchema(schema).chunk.transform(f, s.fromChunk) case Schema.Set(s, _) => set(s) case Schema.Map(k, v, _) => map(k, v) - case s @ Schema.NonEmptyMap(k, v, _) => map(k, v).transform(_.toMap.asInstanceOf[Map[Any, Any]], s.fromMap) + case s @ Schema.NonEmptyMap(k: Schema[kt], v: Schema[vt], _) => map(k, v).transform[NonEmptyMap[kt, vt]](_.toMap.asInstanceOf[Map[kt, vt]], s.fromMap).asInstanceOf[Differ[A]] case Schema.Either(leftSchema, rightSchema, _) => either(fromSchema(leftSchema), fromSchema(rightSchema)) case Schema.Fallback(leftSchema, rightSchema, _, _) => fallback(fromSchema(leftSchema), fromSchema(rightSchema)) case s @ Schema.Lazy(_) => fromSchema(s.schema)