From 18297ab90d814b5fc2f0297bfa9ba4a11810eb22 Mon Sep 17 00:00:00 2001 From: Gabriel Ciuloaica Date: Mon, 26 Jun 2023 15:14:37 +0300 Subject: [PATCH 1/3] draft implementation for supporting java.util.Currency as StandardType --- .../main/scala/zio/schema/codec/AvroCodec.scala | 8 ++++++++ .../scala/zio/schema/codec/AvroPropMarker.scala | 4 ++++ .../scala/zio/schema/codec/AvroSchemaCodec.scala | 6 ++++++ .../scala/zio/schema/codec/BsonSchemaCodec.scala | 1 + .../zio/schema/codec/MessagePackDecoder.scala | 3 ++- .../zio/schema/codec/MessagePackEncoder.scala | 3 +++ .../scala/zio/schema/codec/ProtobufCodec.scala | 6 +++++- .../scala/zio/schema/codec/ThriftCodec.scala | 5 +++++ .../src/main/scala/zio/schema/DeriveGen.scala | 1 + .../src/main/scala/zio/schema/Differ.scala | 16 +++++++++++----- .../shared/src/main/scala/zio/schema/Patch.scala | 7 +++++++ .../src/main/scala/zio/schema/StandardType.scala | 16 +++++++++++++++- 12 files changed, 68 insertions(+), 8 deletions(-) diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroCodec.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroCodec.scala index 97a499e7d..27dba21d2 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroCodec.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroCodec.scala @@ -397,6 +397,12 @@ object AvroCodec { .toEither .left .map(e => DecodeError.MalformedFieldWithPath(Chunk.empty, e.getMessage)) + case StandardType.CurrencyType => + Try(value.asInstanceOf[Utf8]) + .flatMap(c => Try(java.util.Currency.getInstance(c.toString))) + .toEither + .left + .map(e => DecodeError.MalformedFieldWithPath(Chunk.empty, e.getMessage)) } private def decodeMap(value: Any, schema: Schema.Map[Any, Any]) = { @@ -841,6 +847,8 @@ object AvroCodec { case StandardType.ZonedDateTimeType => val zonedDateTime = a.asInstanceOf[java.time.ZonedDateTime] zonedDateTime.toString + case StandardType.CurrencyType => + a.asInstanceOf[java.util.Currency].getCurrencyCode } private def encodeSequence[A](schema: Schema[A], v: Chunk[A]): Any = { diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala index 9217fcced..2c102976f 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala @@ -213,6 +213,7 @@ sealed trait StringType { self => case StringType.OffsetTime => "offsetTime" case StringType.OffsetDateTime => "offsetDateTime" case StringType.ZoneDateTime => "zoneDateTime" + case StringType.Currency => "currency" } } @@ -230,6 +231,7 @@ object StringType { case "offsetTime" => StringType.OffsetTime case "offsetDateTime" => StringType.OffsetDateTime case "zoneDateTime" => StringType.ZoneDateTime + case "currency" => StringType.Currency } } else None @@ -241,6 +243,8 @@ object StringType { case object OffsetTime extends StringType case object OffsetDateTime extends StringType case object ZoneDateTime extends StringType + + case object Currency extends StringType } sealed trait IntType { self => diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala index 6630e6177..d89e4f868 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala @@ -378,6 +378,12 @@ object AvroSchemaCodec extends AvroSchemaCodec { .create(SchemaAvro.Type.STRING) .addMarkerProp(StringDiscriminator(StringType.ZoneDateTime)) ) + case StandardType.CurrencyType => + Right( + SchemaAvro + .create(SchemaAvro.Type.STRING) + .addMarkerProp(StringDiscriminator(StringType.Currency)) + ) } case Optional(codec, _) => for { diff --git a/zio-schema-bson/shared/src/main/scala/zio/schema/codec/BsonSchemaCodec.scala b/zio-schema-bson/shared/src/main/scala/zio/schema/codec/BsonSchemaCodec.scala index 4e57c31fe..e443937e3 100644 --- a/zio-schema-bson/shared/src/main/scala/zio/schema/codec/BsonSchemaCodec.scala +++ b/zio-schema-bson/shared/src/main/scala/zio/schema/codec/BsonSchemaCodec.scala @@ -369,6 +369,7 @@ object BsonSchemaCodec { case StandardType.ZoneIdType => BsonCodec.zoneId.asInstanceOf[BsonCodec[A]] //BsonCodec[java.time.ZoneId] case StandardType.ZoneOffsetType => BsonCodec.zoneOffset.asInstanceOf[BsonCodec[A]] //BsonCodec[java.time.ZoneOffset] + // case StandardType.CurrencyType => BsonCodec.currency.asInstanceOf[BsonCodec[A]] //BsonCodec[java.util.Currency] // TODO: needs implementation in zio-bson } } diff --git a/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackDecoder.scala b/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackDecoder.scala index 821baf4b6..a7475d7d8 100644 --- a/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackDecoder.scala +++ b/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackDecoder.scala @@ -278,7 +278,8 @@ private[codec] class MessagePackDecoder(bytes: Chunk[Byte]) { decodeString(path).map(OffsetDateTime.parse(_)) case StandardType.ZonedDateTimeType => decodeString(path).map(ZonedDateTime.parse(_)) - case _ => fail(path, s"Unsupported primitive type $standardType") + case StandardType.CurrencyType => decodeString(path).map(java.util.Currency.getInstance(_)) + case _ => fail(path, s"Unsupported primitive type $standardType") } private def decodeOptional[A](path: Path, schema: Schema.Optional[A]): Result[Option[A]] = diff --git a/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackEncoder.scala b/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackEncoder.scala index ca267d2fc..d5741d23a 100644 --- a/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackEncoder.scala +++ b/zio-schema-msg-pack/shared/src/main/scala/zio/schema/codec/MessagePackEncoder.scala @@ -242,6 +242,9 @@ private[codec] class MessagePackEncoder { case (StandardType.ZonedDateTimeType, v: ZonedDateTime) => packer.packString(v.toString) () + case (StandardType.CurrencyType, v: java.util.Currency) => + packer.packString(v.getCurrencyCode) + () case (_, _) => throw new NotImplementedError(s"No encoder for $standardType") } 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 01a25f7fd..17611c509 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 @@ -95,6 +95,7 @@ object ProtobufCodec { case StandardType.OffsetTimeType => false case StandardType.OffsetDateTimeType => false case StandardType.ZonedDateTimeType => false + case StandardType.CurrencyType => false } } @@ -400,6 +401,8 @@ object ProtobufCodec { encodePrimitive(fieldNumber, StandardType.StringType, v.toString) case (StandardType.ZonedDateTimeType, v: ZonedDateTime) => encodePrimitive(fieldNumber, StandardType.StringType, v.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) + case (StandardType.CurrencyType, v: java.util.Currency) => + encodePrimitive(fieldNumber, StandardType.StringType, v.getCurrencyCode) case (_, _) => throw new NotImplementedError(s"No encoder for $standardType") } @@ -585,7 +588,8 @@ object ProtobufCodec { OffsetDateTime.parse(stringDecoder(context)) case StandardType.ZonedDateTimeType => ZonedDateTime.parse(stringDecoder(context)) - case st => fail(context, s"Unsupported primitive type $st") + case StandardType.CurrencyType => java.util.Currency.getInstance(stringDecoder(context)) + case st => fail(context, s"Unsupported primitive type $st") } override protected def startCreatingRecord(context: DecoderContext, record: Schema.Record[_]): DecoderContext = diff --git a/zio-schema-thrift/shared/src/main/scala/zio/schema/codec/ThriftCodec.scala b/zio-schema-thrift/shared/src/main/scala/zio/schema/codec/ThriftCodec.scala index 411b5c558..f43dfeb97 100644 --- a/zio-schema-thrift/shared/src/main/scala/zio/schema/codec/ThriftCodec.scala +++ b/zio-schema-thrift/shared/src/main/scala/zio/schema/codec/ThriftCodec.scala @@ -355,6 +355,8 @@ object ThriftCodec { p.writeString(v.toString) case (StandardType.ZonedDateTimeType, v: ZonedDateTime) => p.writeString(v.toString) + case (StandardType.CurrencyType, v: java.util.Currency) => + p.writeString(v.getCurrencyCode) case (_, _) => fail(s"No encoder for $standardType") } @@ -408,6 +410,7 @@ object ThriftCodec { case StandardType.OffsetTimeType => TType.STRING case StandardType.OffsetDateTimeType => TType.STRING case StandardType.ZonedDateTimeType => TType.STRING + case StandardType.CurrencyType => TType.STRING case _ => TType.VOID } @@ -572,6 +575,8 @@ object ThriftCodec { OffsetDateTime.parse(decodeString(context.path)) case StandardType.ZonedDateTimeType => ZonedDateTime.parse(decodeString(context.path)) + case StandardType.CurrencyType => + java.util.Currency.getInstance(decodeString(context.path)) case _ => fail(context, s"Unsupported primitive type $typ") } diff --git a/zio-schema-zio-test/shared/src/main/scala/zio/schema/DeriveGen.scala b/zio-schema-zio-test/shared/src/main/scala/zio/schema/DeriveGen.scala index c2cf96731..4f54d35bf 100644 --- a/zio-schema-zio-test/shared/src/main/scala/zio/schema/DeriveGen.scala +++ b/zio-schema-zio-test/shared/src/main/scala/zio/schema/DeriveGen.scala @@ -513,6 +513,7 @@ object DeriveGen { case StandardType.OffsetTimeType => Gen.offsetTime case StandardType.OffsetDateTimeType => Gen.offsetDateTime case StandardType.ZonedDateTimeType => Gen.zonedDateTime + // case StandardType.CurrencyType => Gen.currency // TODO: needs implementation in zio-test } gen.map(_.asInstanceOf[A]) 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 4f4dee67f..f3eba3faa 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Differ.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Differ.scala @@ -4,12 +4,10 @@ import java.math.{ BigInteger, MathContext } import java.time.temporal.{ ChronoField, ChronoUnit } import java.time.{ DayOfWeek, - Duration => JDuration, Instant, LocalDate, LocalDateTime, LocalTime, - Month => JMonth, MonthDay, OffsetDateTime, OffsetTime, @@ -18,13 +16,13 @@ import java.time.{ YearMonth, ZoneId, ZoneOffset, + Duration => JDuration, + Month => JMonth, ZonedDateTime => JZonedDateTime } -import java.util.UUID - +import java.util.{ Currency, UUID } import scala.annotation.nowarn import scala.collection.immutable.ListMap - import zio.schema.diff.Edit import zio.{ Chunk, ChunkBuilder } @@ -255,6 +253,7 @@ object Differ { case Schema.Primitive(StandardType.OffsetDateTimeType, _) => offsetDateTime case Schema.Primitive(StandardType.ZonedDateTimeType, _) => zonedDateTime case Schema.Primitive(StandardType.ZoneOffsetType, _) => zoneOffset + case Schema.Primitive(StandardType.CurrencyType, _) => currency case Schema.Tuple2(leftSchema, rightSchema, _) => fromSchema(leftSchema) <*> fromSchema(rightSchema) case Schema.Optional(schema, _) => fromSchema(schema).optional case Schema.Sequence(schema, g, f, _, _) => @@ -522,6 +521,13 @@ object Differ { } } + val currency: Differ[Currency] = + (thisValue: Currency, thatValue: Currency) => + if (thisValue == thatValue) + Patch.identical + else + Patch.Currency(thatValue) + def tuple[A, B](left: Differ[A], right: Differ[B]): Differ[(A, B)] = (thisValue: (A, B), thatValue: (A, B)) => (thisValue, thatValue) match { diff --git a/zio-schema/shared/src/main/scala/zio/schema/Patch.scala b/zio-schema/shared/src/main/scala/zio/schema/Patch.scala index 5d5e7e70b..47af8a221 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Patch.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Patch.scala @@ -206,6 +206,13 @@ object Patch { override def invert: Patch[JZonedDateTime] = ZonedDateTime(localDateTimeDiff.invert, zoneIdDiff.invert) } + final case class Currency(currencyCodeDiff: java.util.Currency) extends Patch[java.util.Currency] { + override def patch(input: java.util.Currency): scala.Either[String, java.util.Currency] = + Right(input) + + override def invert: Patch[java.util.Currency] = Currency(currencyCodeDiff) + } + final case class Tuple[A, B](leftDifference: Patch[A], rightDifference: Patch[B]) extends Patch[(A, B)] { override def isIdentical: Boolean = leftDifference.isIdentical && rightDifference.isIdentical diff --git a/zio-schema/shared/src/main/scala/zio/schema/StandardType.scala b/zio-schema/shared/src/main/scala/zio/schema/StandardType.scala index 8be43c8a4..e5e53d53f 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/StandardType.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/StandardType.scala @@ -3,9 +3,10 @@ package zio.schema import java.math.BigInteger import java.time import java.time._ - import zio.Chunk +import java.util.Currency + sealed trait StandardType[A] extends Ordering[A] { self => def tag: String def defaultValue: Either[String, A] @@ -51,6 +52,7 @@ object StandardType { final val OFFSET_DATE_TIME = "offsetDateTime" final val ZONED_DATE_TIME = "zonedDateTime" final val UUID = "uuid" + final val CURRENCY = "currency" } def fromString(tag: String): Option[StandardType[_]] = @@ -85,6 +87,7 @@ object StandardType { case Tags.OFFSET_DATE_TIME => Some(OffsetDateTimeType) case Tags.ZONED_DATE_TIME => Some(ZonedDateTimeType) case Tags.UUID => Some(UUIDType) + case Tags.CURRENCY => Some(CurrencyType) } def apply[A](implicit standardType: StandardType[A]): StandardType[A] = standardType @@ -290,4 +293,15 @@ object StandardType { override def compare(x: ZonedDateTime, y: ZonedDateTime): Int = x.compareTo(y) } + + implicit object CurrencyType extends StandardType[java.util.Currency] { + + override def tag: String = Tags.CURRENCY + + override def defaultValue: Either[String, Currency] = + Right(java.util.Currency.getInstance(java.util.Locale.getDefault)) + + override def compare(x: Currency, y: Currency): Int = x.getCurrencyCode.compareTo(y.getCurrencyCode) + } + } From c8b4b37e622c6eeedc4a00061c84f8cd4e641787 Mon Sep 17 00:00:00 2001 From: Gabriel Ciuloaica Date: Mon, 26 Jun 2023 16:00:36 +0300 Subject: [PATCH 2/3] redefiend the prop marker for Currency --- .../src/main/scala/zio/schema/codec/AvroPropMarker.scala | 8 ++++---- .../src/main/scala/zio/schema/codec/AvroSchemaCodec.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala index 2c102976f..df0aaab24 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala @@ -25,6 +25,10 @@ object AvroPropMarker { override def propName: String = "zio.schema.codec.avro.either" } + case object CurrencyWrapper extends AvroPropMarker { + override def propName: String = "zio.schema.codec.avro.currency" + } + final case class DurationChronoUnit(chronoUnit: ChronoUnit) extends AvroPropMarker { override def propName: String = DurationChronoUnit.propName override def value: Any = chronoUnit.name() @@ -213,7 +217,6 @@ sealed trait StringType { self => case StringType.OffsetTime => "offsetTime" case StringType.OffsetDateTime => "offsetDateTime" case StringType.ZoneDateTime => "zoneDateTime" - case StringType.Currency => "currency" } } @@ -231,7 +234,6 @@ object StringType { case "offsetTime" => StringType.OffsetTime case "offsetDateTime" => StringType.OffsetDateTime case "zoneDateTime" => StringType.ZoneDateTime - case "currency" => StringType.Currency } } else None @@ -243,8 +245,6 @@ object StringType { case object OffsetTime extends StringType case object OffsetDateTime extends StringType case object ZoneDateTime extends StringType - - case object Currency extends StringType } sealed trait IntType { self => diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala index d89e4f868..6a3079cfe 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala @@ -382,7 +382,7 @@ object AvroSchemaCodec extends AvroSchemaCodec { Right( SchemaAvro .create(SchemaAvro.Type.STRING) - .addMarkerProp(StringDiscriminator(StringType.Currency)) + .addMarkerProp(CurrencyWrapper) ) } case Optional(codec, _) => From 35ac3b2fe05794316633b2471249199c18bda726 Mon Sep 17 00:00:00 2001 From: Gabriel Ciuloaica Date: Mon, 26 Jun 2023 16:00:36 +0300 Subject: [PATCH 3/3] redefined the avro prop marker for Currency --- .../src/main/scala/zio/schema/codec/AvroPropMarker.scala | 8 ++++---- .../src/main/scala/zio/schema/codec/AvroSchemaCodec.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala index 2c102976f..df0aaab24 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroPropMarker.scala @@ -25,6 +25,10 @@ object AvroPropMarker { override def propName: String = "zio.schema.codec.avro.either" } + case object CurrencyWrapper extends AvroPropMarker { + override def propName: String = "zio.schema.codec.avro.currency" + } + final case class DurationChronoUnit(chronoUnit: ChronoUnit) extends AvroPropMarker { override def propName: String = DurationChronoUnit.propName override def value: Any = chronoUnit.name() @@ -213,7 +217,6 @@ sealed trait StringType { self => case StringType.OffsetTime => "offsetTime" case StringType.OffsetDateTime => "offsetDateTime" case StringType.ZoneDateTime => "zoneDateTime" - case StringType.Currency => "currency" } } @@ -231,7 +234,6 @@ object StringType { case "offsetTime" => StringType.OffsetTime case "offsetDateTime" => StringType.OffsetDateTime case "zoneDateTime" => StringType.ZoneDateTime - case "currency" => StringType.Currency } } else None @@ -243,8 +245,6 @@ object StringType { case object OffsetTime extends StringType case object OffsetDateTime extends StringType case object ZoneDateTime extends StringType - - case object Currency extends StringType } sealed trait IntType { self => diff --git a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala index d89e4f868..6a3079cfe 100644 --- a/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala +++ b/zio-schema-avro/shared/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala @@ -382,7 +382,7 @@ object AvroSchemaCodec extends AvroSchemaCodec { Right( SchemaAvro .create(SchemaAvro.Type.STRING) - .addMarkerProp(StringDiscriminator(StringType.Currency)) + .addMarkerProp(CurrencyWrapper) ) } case Optional(codec, _) =>