From 0dfbed10b2894e675f66204f79c623e9bd6bc7da Mon Sep 17 00:00:00 2001 From: Michael Nedokushev Date: Sun, 17 Dec 2023 22:17:51 +0000 Subject: [PATCH] Support for all types by derivers (#4) * Implement enum case * Add new Path constructor * Support exhaustive list of primitive types in SchemaEncoder * Support exhaustive list of primitive types for ValueEncoder/ValueDecoder (WIP) * scalafix --- .../zio/apache/parquet/core/Schemas.scala | 50 ++- .../zio/apache/parquet/core/Value.scala | 123 +++++++- .../core/codec/SchemaEncoderDeriver.scala | 88 ++++-- .../parquet/core/codec/ValueDecoder.scala | 8 +- .../core/codec/ValueDecoderDeriver.scala | 115 +++++-- .../parquet/core/codec/ValueEncoder.scala | 8 +- .../core/codec/ValueEncoderDeriver.scala | 93 +++++- .../zio/apache/parquet/core/hadoop/Path.scala | 3 + .../zio/apache/parquet/core/package.scala | 13 + .../core/codec/SchemaEncoderDeriverSpec.scala | 19 +- .../core/codec/ValueCodecDeriverSpec.scala | 286 +++++++++++++++--- 11 files changed, 701 insertions(+), 105 deletions(-) create mode 100644 modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/package.scala diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Schemas.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Schemas.scala index ec6e3b6..632dbfa 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Schemas.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Schemas.scala @@ -22,17 +22,19 @@ object Schemas { case class PrimitiveDef( typeName: PrimitiveTypeName, - annotation: LogicalTypeAnnotation, + annotation: Option[LogicalTypeAnnotation] = None, isOptional: Boolean = false, length: Int = 0 ) extends Def[PrimitiveDef] { - def named(name: String): Type = - Types - .primitive(typeName, repetition(isOptional)) - .as(annotation) + def named(name: String): Type = { + val builder = Types.primitive(typeName, repetition(isOptional)) + + annotation + .fold(builder)(builder.as) .length(length) .named(name) + } def length(len: Int): PrimitiveDef = this.copy(length = len) @@ -104,13 +106,37 @@ object Schemas { import PrimitiveTypeName._ import LogicalTypeAnnotation._ - val string: PrimitiveDef = PrimitiveDef(BINARY, stringType()) - val boolean: PrimitiveDef = PrimitiveDef(INT32, intType(8, false)) - val byte: PrimitiveDef = PrimitiveDef(INT32, intType(8, false)) - val short: PrimitiveDef = PrimitiveDef(INT32, intType(16, true)) - val int: PrimitiveDef = PrimitiveDef(INT32, intType(32, true)) - val long: PrimitiveDef = PrimitiveDef(INT64, intType(64, true)) - val uuid: PrimitiveDef = PrimitiveDef(FIXED_LEN_BYTE_ARRAY, uuidType()).length(16) + // https://github.com/apache/parquet-format/blob/master/LogicalTypes.md + def enum0: PrimitiveDef = PrimitiveDef(BINARY, Some(enumType())) + val string: PrimitiveDef = PrimitiveDef(BINARY, Some(stringType())) + val boolean: PrimitiveDef = PrimitiveDef(BOOLEAN) + val byte: PrimitiveDef = PrimitiveDef(INT32, Some(intType(8, false))) + val short: PrimitiveDef = PrimitiveDef(INT32, Some(intType(16, true))) + val int: PrimitiveDef = PrimitiveDef(INT32, Some(intType(32, true))) + val long: PrimitiveDef = PrimitiveDef(INT64, Some(intType(64, true))) + val float: PrimitiveDef = PrimitiveDef(FLOAT) + val double: PrimitiveDef = PrimitiveDef(DOUBLE) + val binary: PrimitiveDef = PrimitiveDef(BINARY) + val char: PrimitiveDef = byte + val uuid: PrimitiveDef = PrimitiveDef(FIXED_LEN_BYTE_ARRAY, Some(uuidType())).length(16) + val bigDecimal: PrimitiveDef = PrimitiveDef(INT64, Some(decimalType(DECIMAL_PRECISION, DECIMAL_SCALE))) + val bigInteger: PrimitiveDef = PrimitiveDef(BINARY) + val dayOfWeek: PrimitiveDef = byte + val monthType: PrimitiveDef = byte + val monthDay: PrimitiveDef = PrimitiveDef(FIXED_LEN_BYTE_ARRAY).length(2) + val period: PrimitiveDef = PrimitiveDef(FIXED_LEN_BYTE_ARRAY).length(12) + val year: PrimitiveDef = PrimitiveDef(INT32, Some(intType(16, false))) + val yearMonth: PrimitiveDef = PrimitiveDef(FIXED_LEN_BYTE_ARRAY).length(4) + val zoneId: PrimitiveDef = string + val zoneOffset: PrimitiveDef = string + val duration: PrimitiveDef = PrimitiveDef(INT64, Some(intType(64, false))) + val instant: PrimitiveDef = PrimitiveDef(INT64, Some(intType(64, false))) + val localDate: PrimitiveDef = PrimitiveDef(INT32, Some(dateType())) + val localTime: PrimitiveDef = PrimitiveDef(INT32, Some(timeType(true, TimeUnit.MILLIS))) + val localDateTime: PrimitiveDef = PrimitiveDef(INT64, Some(timestampType(true, TimeUnit.MILLIS))) + val offsetTime: PrimitiveDef = PrimitiveDef(INT32, Some(timeType(false, TimeUnit.MILLIS))) + val offsetDateTime: PrimitiveDef = PrimitiveDef(INT64, Some(timestampType(false, TimeUnit.MILLIS))) + val zonedDateTime: PrimitiveDef = offsetDateTime def record(fields: Chunk[Type]): RecordDef = RecordDef(fields) def list(element: Type): ListDef = ListDef(element) diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Value.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Value.scala index 9b088f5..42973a8 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Value.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/Value.scala @@ -4,7 +4,26 @@ import org.apache.parquet.io.api.{ Binary, RecordConsumer } import org.apache.parquet.schema.Type import zio.Chunk -import java.nio.ByteBuffer +import java.math.{ BigDecimal, BigInteger } +import java.nio.{ ByteBuffer, ByteOrder } +import java.time.{ + DayOfWeek, + Duration, + Instant, + LocalDate, + LocalDateTime, + LocalTime, + Month, + MonthDay, + OffsetDateTime, + OffsetTime, + Period, + Year, + YearMonth, + ZoneId, + ZoneOffset, + ZonedDateTime +} import java.util.UUID sealed trait Value { @@ -179,8 +198,11 @@ object Value { def boolean(v: Boolean) = PrimitiveValue.BooleanValue(v) + def byte(v: Byte) = + int(v.toInt) + def short(v: Short) = - PrimitiveValue.Int32Value(v.toInt) + int(v.toInt) def int(v: Int) = PrimitiveValue.Int32Value(v) @@ -198,7 +220,7 @@ object Value { PrimitiveValue.BinaryValue(Binary.fromConstantByteArray(v.toArray)) def char(v: Char) = - PrimitiveValue.Int32Value(v.toInt) + int(v.toInt) def uuid(v: UUID) = { val bb = ByteBuffer.wrap(Array.ofDim(16)) @@ -209,6 +231,101 @@ object Value { PrimitiveValue.BinaryValue(Binary.fromConstantByteArray(bb.array())) } + def bigDecimal(v: BigDecimal) = + long(v.unscaledValue.longValue) + + def bigInteger(v: BigInteger) = + PrimitiveValue.BinaryValue(Binary.fromConstantByteArray(v.toByteArray)) + + def dayOfWeek(v: DayOfWeek) = + byte(v.getValue.toByte) + + def month(v: Month) = + byte(v.getValue.toByte) + + def monthDay(v: MonthDay) = { + val bb = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN) + + bb.put(v.getMonthValue.toByte) + bb.put(v.getDayOfMonth.toByte) + + PrimitiveValue.BinaryValue(Binary.fromReusedByteArray(bb.array())) + } + + def period(v: Period) = { + val bb = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN) + + bb.putInt(v.getYears) + bb.putInt(v.getMonths) + bb.putInt(v.getDays) + + PrimitiveValue.BinaryValue(Binary.fromReusedByteArray(bb.array())) + } + + def year(v: Year) = + short(v.getValue.toShort) + + def yearMonth(v: YearMonth) = { + val bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + + bb.putShort(v.getYear.toShort) + bb.putShort(v.getMonthValue.toShort) + + PrimitiveValue.BinaryValue(Binary.fromReusedByteArray(bb.array())) + } + + def zoneId(v: ZoneId) = + string(v.getId) + + def zoneOffset(v: ZoneOffset) = + string(v.getId) + + def duration(v: Duration) = + long(v.toMillis) + + def instant(v: Instant) = + long(v.toEpochMilli) + + def localDate(v: LocalDate) = + int(v.toEpochDay.toInt) + + def localTime(v: LocalTime) = + int((v.toNanoOfDay / MICROS_FACTOR).toInt) + + def localDateTime(v: LocalDateTime) = { + val dateMillis = v.toLocalDate.toEpochDay * MILLIS_PER_DAY + val timeMillis = v.toLocalTime.toNanoOfDay / MICROS_FACTOR + val epochMillis = dateMillis + timeMillis + + long(epochMillis) + } + + def offsetTime(v: OffsetTime) = { + val timeMillis = v.toLocalTime.toNanoOfDay / MICROS_FACTOR + val offsetMillis = v.getOffset.getTotalSeconds * MILLIS_FACTOR + val dayMillis = timeMillis - offsetMillis + + int(dayMillis.toInt) + } + + def offsetDateTime(v: OffsetDateTime) = { + val dateMillis = v.toLocalDate.toEpochDay * MILLIS_PER_DAY + val timeMillis = v.toLocalTime.toNanoOfDay / MICROS_FACTOR + val offsetMillis = v.getOffset.getTotalSeconds * MILLIS_FACTOR + val epochMillis = dateMillis + timeMillis - offsetMillis + + long(epochMillis) + } + + def zonedDateTime(v: ZonedDateTime) = { + val dateMillis = v.toLocalDate.toEpochDay * MILLIS_PER_DAY + val timeMillis = v.toLocalTime.toNanoOfDay / MICROS_FACTOR + val offsetMillis = v.getOffset.getTotalSeconds * MILLIS_FACTOR + val epochMillis = dateMillis + timeMillis - offsetMillis + + long(epochMillis) + } + def record(r: Map[String, Value]) = GroupValue.RecordValue(r) diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriver.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriver.scala index 2b972d6..9939d5b 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriver.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriver.scala @@ -1,6 +1,7 @@ package me.mnedokushev.zio.apache.parquet.core.codec import me.mnedokushev.zio.apache.parquet.core.Schemas +import me.mnedokushev.zio.apache.parquet.core.Schemas.PrimitiveDef import org.apache.parquet.schema.Type import zio.Chunk import zio.schema.{ Deriver, Schema, StandardType } @@ -31,32 +32,83 @@ object SchemaEncoderDeriver { `enum`: Schema.Enum[A], cases: => Chunk[Deriver.WrappedF[SchemaEncoder, _]], summoned: => Option[SchemaEncoder[A]] - ): SchemaEncoder[A] = ??? + ): SchemaEncoder[A] = new SchemaEncoder[A] { + override def encode(schema: Schema[A], name: String, optional: Boolean): Type = + Schemas.enum0.optionality(optional).named(name) + } override def derivePrimitive[A]( st: StandardType[A], summoned: => Option[SchemaEncoder[A]] ): SchemaEncoder[A] = new SchemaEncoder[A] { - override def encode(schema: Schema[A], name: String, optional: Boolean): Type = + override def encode(schema: Schema[A], name: String, optional: Boolean): Type = { + def tpe(prim: PrimitiveDef) = + prim.optionality(optional).named(name) + st match { - case StandardType.StringType => - Schemas.string.optionality(optional).named(name) - case StandardType.BoolType => - Schemas.boolean.optionality(optional).named(name) - case StandardType.ByteType => - Schemas.byte.optionality(optional).named(name) - case StandardType.ShortType => - Schemas.short.optionality(optional).named(name) - case StandardType.IntType => - Schemas.int.optionality(optional).named(name) - case StandardType.LongType => - Schemas.long.optionality(optional).named(name) - // TODO: add the other types - case StandardType.UUIDType => - Schemas.uuid.optionality(optional).named(name) - case _ => ??? + case StandardType.StringType => + tpe(Schemas.string) + case StandardType.BoolType => + tpe(Schemas.boolean) + case StandardType.ByteType => + tpe(Schemas.byte) + case StandardType.ShortType => + tpe(Schemas.short) + case StandardType.IntType => + tpe(Schemas.int) + case StandardType.LongType => + tpe(Schemas.long) + case StandardType.FloatType => + tpe(Schemas.float) + case StandardType.DoubleType => + tpe(Schemas.double) + case StandardType.BinaryType => + tpe(Schemas.binary) + case StandardType.CharType => + tpe(Schemas.char) + case StandardType.UUIDType => + tpe(Schemas.uuid) + case StandardType.BigDecimalType => + tpe(Schemas.bigDecimal) + case StandardType.BigIntegerType => + tpe(Schemas.bigInteger) + case StandardType.DayOfWeekType => + tpe(Schemas.dayOfWeek) + case StandardType.MonthType => + tpe(Schemas.monthType) + case StandardType.MonthDayType => + tpe(Schemas.monthDay) + case StandardType.PeriodType => + tpe(Schemas.period) + case StandardType.YearType => + tpe(Schemas.year) + case StandardType.YearMonthType => + tpe(Schemas.yearMonth) + case StandardType.ZoneIdType => + tpe(Schemas.zoneId) + case StandardType.ZoneOffsetType => + tpe(Schemas.zoneOffset) + case StandardType.DurationType => + tpe(Schemas.duration) + case StandardType.InstantType => + tpe(Schemas.instant) + case StandardType.LocalDateType => + tpe(Schemas.localDate) + case StandardType.LocalTimeType => + tpe(Schemas.localTime) + case StandardType.LocalDateTimeType => + tpe(Schemas.localDateTime) + case StandardType.OffsetTimeType => + tpe(Schemas.offsetTime) + case StandardType.OffsetDateTimeType => + tpe(Schemas.offsetDateTime) + case StandardType.ZonedDateTimeType => + tpe(Schemas.zonedDateTime) + case StandardType.UnitType => + throw EncoderError("Unit type is unsupported") } + } } override def deriveOption[A]( diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoder.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoder.scala index 06d3afd..28bd2ef 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoder.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoder.scala @@ -3,11 +3,17 @@ package me.mnedokushev.zio.apache.parquet.core.codec import me.mnedokushev.zio.apache.parquet.core.Value import zio._ -trait ValueDecoder[+A] { +trait ValueDecoder[+A] { self => def decode(value: Value): A def decodeZIO(value: Value): Task[A] = ZIO.attempt(decode(value)) + def map[B](f: A => B): ValueDecoder[B] = + new ValueDecoder[B] { + override def decode(value: Value): B = + f(self.decode(value)) + } + } diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoderDeriver.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoderDeriver.scala index f057efa..6654e68 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoderDeriver.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueDecoderDeriver.scala @@ -1,12 +1,29 @@ package me.mnedokushev.zio.apache.parquet.core.codec -import me.mnedokushev.zio.apache.parquet.core.Value import me.mnedokushev.zio.apache.parquet.core.Value.{ GroupValue, PrimitiveValue } +import me.mnedokushev.zio.apache.parquet.core.{ DECIMAL_SCALE, MICROS_FACTOR, MILLIS_PER_DAY, Value } import zio._ import zio.schema._ -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets +import java.math.{ BigDecimal, BigInteger } +import java.nio.{ ByteBuffer, ByteOrder } +import java.time.{ + DayOfWeek, + Instant, + LocalDate, + LocalDateTime, + LocalTime, + Month, + MonthDay, + OffsetDateTime, + OffsetTime, + Period, + Year, + YearMonth, + ZoneId, + ZoneOffset, + ZonedDateTime +} import java.util.UUID object ValueDecoderDeriver { @@ -47,38 +64,102 @@ object ValueDecoderDeriver { `enum`: Schema.Enum[A], cases: => Chunk[Deriver.WrappedF[ValueDecoder, _]], summoned: => Option[ValueDecoder[A]] - ): ValueDecoder[A] = ??? + ): ValueDecoder[A] = new ValueDecoder[A] { + override def decode(value: Value): A = { + val casesMap = `enum`.cases.map { case0 => + case0.id -> case0.schema.asInstanceOf[Schema.CaseClass0[A]].defaultConstruct() + }.toMap + + derivePrimitive(StandardType.StringType, summoned = None).map { case0 => + casesMap.getOrElse(case0, throw DecoderError(s"Failed to decode enum for id $case0")) + }.decode(value) + } + } override def derivePrimitive[A]( st: StandardType[A], summoned: => Option[ValueDecoder[A]] ): ValueDecoder[A] = new ValueDecoder[A] { + + private def localTime(v: Int) = + LocalTime.ofNanoOfDay(v * MICROS_FACTOR) + + private def localDateTime(v: Long) = { + val epochDay = v / MILLIS_PER_DAY + val nanoOfDay = (v - (epochDay * MILLIS_PER_DAY)) * MICROS_FACTOR + + LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay)) + } + override def decode(value: Value): A = (st, value) match { - case (StandardType.StringType, PrimitiveValue.BinaryValue(v)) => - new String(v.getBytes, StandardCharsets.UTF_8) - case (StandardType.BoolType, PrimitiveValue.BooleanValue(v)) => + case (StandardType.StringType, PrimitiveValue.BinaryValue(v)) => + v.toStringUsingUTF8 + case (StandardType.BoolType, PrimitiveValue.BooleanValue(v)) => v - case (StandardType.ByteType, PrimitiveValue.Int32Value(v)) => + case (StandardType.ByteType, PrimitiveValue.Int32Value(v)) => v.toByte - case (StandardType.ShortType, PrimitiveValue.Int32Value(v)) => + case (StandardType.ShortType, PrimitiveValue.Int32Value(v)) => v.toShort - case (StandardType.IntType, PrimitiveValue.Int32Value(v)) => + case (StandardType.IntType, PrimitiveValue.Int32Value(v)) => v - case (StandardType.LongType, PrimitiveValue.Int64Value(v)) => + case (StandardType.LongType, PrimitiveValue.Int64Value(v)) => v - case (StandardType.FloatType, PrimitiveValue.FloatValue(v)) => + case (StandardType.FloatType, PrimitiveValue.FloatValue(v)) => v - case (StandardType.DoubleType, PrimitiveValue.DoubleValue(v)) => + case (StandardType.DoubleType, PrimitiveValue.DoubleValue(v)) => v - case (StandardType.BinaryType, PrimitiveValue.BinaryValue(v)) => + case (StandardType.BinaryType, PrimitiveValue.BinaryValue(v)) => Chunk.fromArray(v.getBytes) - case (StandardType.CharType, PrimitiveValue.Int32Value(v)) => + case (StandardType.CharType, PrimitiveValue.Int32Value(v)) => v.toChar - case (StandardType.UUIDType, PrimitiveValue.BinaryValue(v)) => + case (StandardType.UUIDType, PrimitiveValue.BinaryValue(v)) => val bb = ByteBuffer.wrap(v.getBytes) + new UUID(bb.getLong, bb.getLong) - case (other, _) => + case (StandardType.BigDecimalType, PrimitiveValue.Int64Value(v)) => + BigDecimal.valueOf(v, DECIMAL_SCALE) + case (StandardType.BigIntegerType, PrimitiveValue.BinaryValue(v)) => + new BigInteger(v.getBytes) + case (StandardType.DayOfWeekType, PrimitiveValue.Int32Value(v)) => + DayOfWeek.of(v) + case (StandardType.MonthType, PrimitiveValue.Int32Value(v)) => + Month.of(v) + case (StandardType.MonthDayType, PrimitiveValue.BinaryValue(v)) => + val bb = ByteBuffer.wrap(v.getBytes).order(ByteOrder.LITTLE_ENDIAN) + + MonthDay.of(bb.get.toInt, bb.get.toInt) + case (StandardType.PeriodType, PrimitiveValue.BinaryValue(v)) => + val bb = ByteBuffer.wrap(v.getBytes).order(ByteOrder.LITTLE_ENDIAN) + + Period.of(bb.getInt, bb.getInt, bb.getInt) + case (StandardType.YearType, PrimitiveValue.Int32Value(v)) => + Year.of(v) + case (StandardType.YearMonthType, PrimitiveValue.BinaryValue(v)) => + val bb = ByteBuffer.wrap(v.getBytes).order(ByteOrder.LITTLE_ENDIAN) + + YearMonth.of(bb.getShort.toInt, bb.getShort.toInt) + case (StandardType.ZoneIdType, PrimitiveValue.BinaryValue(v)) => + ZoneId.of(v.toStringUsingUTF8) + case (StandardType.ZoneOffsetType, PrimitiveValue.BinaryValue(v)) => + ZoneOffset.of(v.toStringUsingUTF8) + case (StandardType.DurationType, PrimitiveValue.Int64Value(v)) => + Duration.fromMillis(v) + case (StandardType.InstantType, PrimitiveValue.Int64Value(v)) => + Instant.ofEpochMilli(v) + case (StandardType.LocalDateType, PrimitiveValue.Int32Value(v)) => + LocalDate.ofEpochDay(v.toLong) + case (StandardType.LocalTimeType, PrimitiveValue.Int32Value(v)) => + localTime(v) + case (StandardType.LocalDateTimeType, PrimitiveValue.Int64Value(v)) => + localDateTime(v) + case (StandardType.OffsetTimeType, PrimitiveValue.Int32Value(v)) => + OffsetTime.of(localTime(v), ZoneOffset.UTC) + case (StandardType.OffsetDateTimeType, PrimitiveValue.Int64Value(v)) => + OffsetDateTime.of(localDateTime(v), ZoneOffset.UTC) + case (StandardType.ZonedDateTimeType, PrimitiveValue.Int64Value(v)) => + ZonedDateTime.of(localDateTime(v), ZoneId.of("Z")) + case (other, _) => throw DecoderError(s"Unsupported ZIO Schema StandartType $other") } } diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoder.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoder.scala index 73203e6..74e8179 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoder.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoder.scala @@ -3,11 +3,17 @@ package me.mnedokushev.zio.apache.parquet.core.codec import me.mnedokushev.zio.apache.parquet.core.Value import zio._ -trait ValueEncoder[-A] { +trait ValueEncoder[-A] { self => def encode(value: A): Value def encodeZIO(value: A): Task[Value] = ZIO.attemptBlocking(encode(value)) + def contramap[B](f: B => A): ValueEncoder[B] = + new ValueEncoder[B] { + override def encode(value: B): Value = + self.encode(f(value)) + } + } diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoderDeriver.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoderDeriver.scala index 6f2abc2..f1ad471 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoderDeriver.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueEncoderDeriver.scala @@ -4,6 +4,25 @@ import me.mnedokushev.zio.apache.parquet.core.Value import zio.Chunk import zio.schema.{ Deriver, Schema, StandardType } +import java.math.{ BigDecimal, BigInteger } +import java.time.{ + DayOfWeek, + Duration, + Instant, + LocalDate, + LocalDateTime, + LocalTime, + Month, + MonthDay, + OffsetDateTime, + OffsetTime, + Period, + Year, + YearMonth, + ZoneId, + ZoneOffset, + ZonedDateTime +} import java.util.UUID object ValueEncoderDeriver { @@ -34,7 +53,19 @@ object ValueEncoderDeriver { `enum`: Schema.Enum[A], cases: => Chunk[Deriver.WrappedF[ValueEncoder, _]], summoned: => Option[ValueEncoder[A]] - ): ValueEncoder[A] = ??? + ): ValueEncoder[A] = new ValueEncoder[A] { + override def encode(value: A): Value = { + val casesMap = `enum`.cases.map { case0 => + case0.schema.asInstanceOf[Schema.CaseClass0[A]].defaultConstruct() -> case0.id + }.toMap + + derivePrimitive(StandardType.StringType, summoned = None) + .contramap[A] { case0 => + casesMap.getOrElse(case0, throw EncoderError(s"Failed to encode enum for value $case0")) + } + .encode(value) + } + } override def derivePrimitive[A]( st: StandardType[A], @@ -43,29 +74,65 @@ object ValueEncoderDeriver { new ValueEncoder[A] { override def encode(value: A): Value = (st, value) match { - case (StandardType.StringType, v: String) => + case (StandardType.StringType, v: String) => Value.string(v) - case (StandardType.BoolType, v: Boolean) => + case (StandardType.BoolType, v: Boolean) => Value.boolean(v) - case (StandardType.ByteType, v: Byte) => + case (StandardType.ByteType, v: Byte) => Value.int(v.toInt) - case (StandardType.ShortType, v: Short) => + case (StandardType.ShortType, v: Short) => Value.short(v) - case (StandardType.IntType, v: Int) => + case (StandardType.IntType, v: Int) => Value.int(v) - case (StandardType.LongType, v: Long) => + case (StandardType.LongType, v: Long) => Value.long(v) - case (StandardType.FloatType, v: Float) => + case (StandardType.FloatType, v: Float) => Value.float(v) - case (StandardType.DoubleType, v: Double) => + case (StandardType.DoubleType, v: Double) => Value.double(v) - case (StandardType.BinaryType, v: Chunk[_]) => + case (StandardType.BinaryType, v: Chunk[_]) => Value.binary(v.asInstanceOf[Chunk[Byte]]) - case (StandardType.CharType, v: Char) => + case (StandardType.CharType, v: Char) => Value.char(v) - case (StandardType.UUIDType, v: UUID) => + case (StandardType.UUIDType, v: UUID) => Value.uuid(v) - case (other, _) => + case (StandardType.BigDecimalType, v: BigDecimal) => + Value.bigDecimal(v) + case (StandardType.BigIntegerType, v: BigInteger) => + Value.bigInteger(v) + case (StandardType.DayOfWeekType, v: DayOfWeek) => + Value.dayOfWeek(v) + case (StandardType.MonthType, v: Month) => + Value.month(v) + case (StandardType.MonthDayType, v: MonthDay) => + Value.monthDay(v) + case (StandardType.PeriodType, v: Period) => + Value.period(v) + case (StandardType.YearType, v: Year) => + Value.year(v) + case (StandardType.YearMonthType, v: YearMonth) => + Value.yearMonth(v) + case (StandardType.ZoneIdType, v: ZoneId) => + Value.zoneId(v) + case (StandardType.ZoneOffsetType, v: ZoneOffset) => + Value.zoneOffset(v) + case (StandardType.DurationType, v: Duration) => + Value.duration(v) + case (StandardType.InstantType, v: Instant) => + Value.instant(v) + case (StandardType.LocalDateType, v: LocalDate) => + Value.localDate(v) + case (StandardType.LocalTimeType, v: LocalTime) => + Value.localTime(v) + case (StandardType.LocalDateTimeType, v: LocalDateTime) => + Value.localDateTime(v) + case (StandardType.OffsetTimeType, v: OffsetTime) => + Value.offsetTime(v) + case (StandardType.OffsetDateTimeType, v: OffsetDateTime) => + Value.offsetDateTime(v) + case (StandardType.ZonedDateTimeType, v: ZonedDateTime) => + Value.zonedDateTime(v) + case (other, _) => throw EncoderError(s"Unsupported ZIO Schema StandardType $other") } } diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/hadoop/Path.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/hadoop/Path.scala index 709da9b..bc44b00 100644 --- a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/hadoop/Path.scala +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/hadoop/Path.scala @@ -36,4 +36,7 @@ object Path { def apply(path: JPath): Path = Path(new HadoopPath(new URI("file", null, path.toAbsolutePath.toString, null, null))) + def apply(uri: URI): Path = + Path(new HadoopPath(uri)) + } diff --git a/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/package.scala b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/package.scala new file mode 100644 index 0000000..bff5e61 --- /dev/null +++ b/modules/core/src/main/scala/me/mnedokushev/zio/apache/parquet/core/package.scala @@ -0,0 +1,13 @@ +package me.mnedokushev.zio.apache.parquet + +package object core { + + val MILLIS_PER_DAY = 86400000L + val NANOS_PER_DAY = 86400000000000L + val MILLIS_FACTOR = 1000L + val MICROS_FACTOR = 1000000L + val NANOS_FACTOR = 1000000000L + val DECIMAL_PRECISION = 11 + val DECIMAL_SCALE = 2 + +} diff --git a/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriverSpec.scala b/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriverSpec.scala index ffd2b6a..8d10f9e 100644 --- a/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriverSpec.scala +++ b/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/SchemaEncoderDeriverSpec.scala @@ -11,6 +11,15 @@ import java.util.UUID object SchemaEncoderDeriverSpec extends ZIOSpecDefault { + sealed trait MyEnum + object MyEnum { + case object Started extends MyEnum + case object InProgress extends MyEnum + case object Done extends MyEnum + + implicit val schema: Schema[MyEnum] = DeriveSchema.gen[MyEnum] + } + case class Record(a: Int, b: Option[String]) object Record { implicit val schema: Schema[Record] = DeriveSchema.gen[Record] @@ -166,8 +175,7 @@ object SchemaEncoderDeriverSpec extends ZIOSpecDefault { .reduce(_ && _) }, test("map") { - val name = "mymap" - + val name = "mymap" val encoder = Derive.derive[SchemaEncoder, Map[String, Int]](SchemaEncoderDeriver.default) val tpe = encoder.encode(Schema.map[String, Int], name, optional = true) @@ -177,6 +185,13 @@ object SchemaEncoderDeriverSpec extends ZIOSpecDefault { .optional .named(name) ) + }, + test("enum") { + val name = "myenum" + val encoder = Derive.derive[SchemaEncoder, MyEnum](SchemaEncoderDeriver.default) + val tpe = encoder.encode(Schema[MyEnum], name, optional = true) + + assertTrue(tpe == Schemas.enum0.optional.named(name)) } // test("summoned") { // // @nowarn annotation is needed to avoid having 'variable is not used' compiler error diff --git a/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueCodecDeriverSpec.scala b/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueCodecDeriverSpec.scala index 1da1451..7189a68 100644 --- a/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueCodecDeriverSpec.scala +++ b/modules/core/src/test/scala/me/mnedokushev/zio/apache/parquet/core/codec/ValueCodecDeriverSpec.scala @@ -2,10 +2,30 @@ package me.mnedokushev.zio.apache.parquet.core.codec //import me.mnedokushev.zio.apache.parquet.core.Value //import me.mnedokushev.zio.apache.parquet.core.Value.PrimitiveValue +import me.mnedokushev.zio.apache.parquet.core.{ NANOS_FACTOR, NANOS_PER_DAY, Value } import zio._ import zio.schema._ +import zio.test.Assertion._ import zio.test._ +import java.math.{ BigDecimal, BigInteger } +import java.time.{ + DayOfWeek, + Instant, + LocalDate, + LocalDateTime, + LocalTime, + Month, + MonthDay, + OffsetDateTime, + OffsetTime, + Period, + Year, + YearMonth, + ZoneId, + ZoneOffset, + ZonedDateTime +} import java.util.UUID //import java.nio.ByteBuffer @@ -14,6 +34,15 @@ import java.util.UUID object ValueCodecDeriverSpec extends ZIOSpecDefault { + sealed trait MyEnum + object MyEnum { + case object Started extends MyEnum + case object InProgress extends MyEnum + case object Done extends MyEnum + + implicit val schema: Schema[MyEnum] = DeriveSchema.gen[MyEnum] + } + case class Record(a: Int, b: Boolean, c: Option[String], d: List[Int], e: Map[String, Int]) object Record { implicit val schema: Schema[Record] = DeriveSchema.gen[Record] @@ -27,45 +56,191 @@ object ValueCodecDeriverSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("ValueCodecDeriverSpec")( test("primitive") { - val stringEncoder = Derive.derive[ValueEncoder, String](ValueEncoderDeriver.default) - val booleanEncoder = Derive.derive[ValueEncoder, Boolean](ValueEncoderDeriver.default) - val byteEncoder = Derive.derive[ValueEncoder, Byte](ValueEncoderDeriver.default) - val shortEncoder = Derive.derive[ValueEncoder, Short](ValueEncoderDeriver.default) - val intEncoder = Derive.derive[ValueEncoder, Int](ValueEncoderDeriver.default) - val longEncoder = Derive.derive[ValueEncoder, Long](ValueEncoderDeriver.default) - val uuidEncoder = Derive.derive[ValueEncoder, UUID](ValueEncoderDeriver.default) - - val stringDecoder = Derive.derive[ValueDecoder, String](ValueDecoderDeriver.default) - val booleanDecoder = Derive.derive[ValueDecoder, Boolean](ValueDecoderDeriver.default) - val byteDecoder = Derive.derive[ValueDecoder, Byte](ValueDecoderDeriver.default) - val shortDecoder = Derive.derive[ValueDecoder, Short](ValueDecoderDeriver.default) - val intDecoder = Derive.derive[ValueDecoder, Int](ValueDecoderDeriver.default) - val longDecoder = Derive.derive[ValueDecoder, Long](ValueDecoderDeriver.default) - val uuidDecoder = Derive.derive[ValueDecoder, UUID](ValueDecoderDeriver.default) - - val stringPayload = "foo" - val booleanPayload = false - val bytePayload = 10.toByte - val shortPayload = 30.toShort - val intPayload = 254 - val longPayload = 398812L - val uuidPayload = UUID.randomUUID() + val stringEncoder = Derive.derive[ValueEncoder, String](ValueEncoderDeriver.default) + val booleanEncoder = Derive.derive[ValueEncoder, Boolean](ValueEncoderDeriver.default) + val byteEncoder = Derive.derive[ValueEncoder, Byte](ValueEncoderDeriver.default) + val shortEncoder = Derive.derive[ValueEncoder, Short](ValueEncoderDeriver.default) + val intEncoder = Derive.derive[ValueEncoder, Int](ValueEncoderDeriver.default) + val longEncoder = Derive.derive[ValueEncoder, Long](ValueEncoderDeriver.default) + val floatEncoder = Derive.derive[ValueEncoder, Float](ValueEncoderDeriver.default) + val doubleEncoder = Derive.derive[ValueEncoder, Double](ValueEncoderDeriver.default) + val binaryEncoder = Derive.derive[ValueEncoder, Chunk[Byte]](ValueEncoderDeriver.default) + val charEncoder = Derive.derive[ValueEncoder, Char](ValueEncoderDeriver.default) + val uuidEncoder = Derive.derive[ValueEncoder, UUID](ValueEncoderDeriver.default) + val bigDecimalEncoder = Derive.derive[ValueEncoder, BigDecimal](ValueEncoderDeriver.default) + val bigIntegerEncoder = Derive.derive[ValueEncoder, BigInteger](ValueEncoderDeriver.default) + val dayOfWeekEncoder = Derive.derive[ValueEncoder, DayOfWeek](ValueEncoderDeriver.default) + val monthEncoder = Derive.derive[ValueEncoder, Month](ValueEncoderDeriver.default) + val monthDayEncoder = Derive.derive[ValueEncoder, MonthDay](ValueEncoderDeriver.default) + val periodEncoder = Derive.derive[ValueEncoder, Period](ValueEncoderDeriver.default) + val yearEncoder = Derive.derive[ValueEncoder, Year](ValueEncoderDeriver.default) + val yearMonthEncoder = Derive.derive[ValueEncoder, YearMonth](ValueEncoderDeriver.default) + val zoneIdEncoder = Derive.derive[ValueEncoder, ZoneId](ValueEncoderDeriver.default) + val zoneOffsetEncoder = Derive.derive[ValueEncoder, ZoneOffset](ValueEncoderDeriver.default) + val durationEncoder = Derive.derive[ValueEncoder, Duration](ValueEncoderDeriver.default) + val instantEncoder = Derive.derive[ValueEncoder, Instant](ValueEncoderDeriver.default) + val localDateEncoder = Derive.derive[ValueEncoder, LocalDate](ValueEncoderDeriver.default) + val localTimeEncoder = Derive.derive[ValueEncoder, LocalTime](ValueEncoderDeriver.default) + val localDateTimeEncoder = Derive.derive[ValueEncoder, LocalDateTime](ValueEncoderDeriver.default) + val offsetTimeEncoder = Derive.derive[ValueEncoder, OffsetTime](ValueEncoderDeriver.default) + val offsetDateTimeEncoder = Derive.derive[ValueEncoder, OffsetDateTime](ValueEncoderDeriver.default) + val zonedDateTimeEncoder = Derive.derive[ValueEncoder, ZonedDateTime](ValueEncoderDeriver.default) + + val stringDecoder = Derive.derive[ValueDecoder, String](ValueDecoderDeriver.default) + val booleanDecoder = Derive.derive[ValueDecoder, Boolean](ValueDecoderDeriver.default) + val byteDecoder = Derive.derive[ValueDecoder, Byte](ValueDecoderDeriver.default) + val shortDecoder = Derive.derive[ValueDecoder, Short](ValueDecoderDeriver.default) + val intDecoder = Derive.derive[ValueDecoder, Int](ValueDecoderDeriver.default) + val longDecoder = Derive.derive[ValueDecoder, Long](ValueDecoderDeriver.default) + val floatDecoder = Derive.derive[ValueDecoder, Float](ValueDecoderDeriver.default) + val doubleDecoder = Derive.derive[ValueDecoder, Double](ValueDecoderDeriver.default) + val binaryDecoder = Derive.derive[ValueDecoder, Chunk[Byte]](ValueDecoderDeriver.default) + val charDecoder = Derive.derive[ValueDecoder, Char](ValueDecoderDeriver.default) + val uuidDecoder = Derive.derive[ValueDecoder, UUID](ValueDecoderDeriver.default) + val bigDecimalDecoder = Derive.derive[ValueDecoder, BigDecimal](ValueDecoderDeriver.default) + val bigIntegerDecoder = Derive.derive[ValueDecoder, BigInteger](ValueDecoderDeriver.default) + val dayOfWeekDecoder = Derive.derive[ValueDecoder, DayOfWeek](ValueDecoderDeriver.default) + val monthDecoder = Derive.derive[ValueDecoder, Month](ValueDecoderDeriver.default) + val monthDayDecoder = Derive.derive[ValueDecoder, MonthDay](ValueDecoderDeriver.default) + val periodDecoder = Derive.derive[ValueDecoder, Period](ValueDecoderDeriver.default) + val yearDecoder = Derive.derive[ValueDecoder, Year](ValueDecoderDeriver.default) + val yearMonthDecoder = Derive.derive[ValueDecoder, YearMonth](ValueDecoderDeriver.default) + val zoneIdDecoder = Derive.derive[ValueDecoder, ZoneId](ValueDecoderDeriver.default) + val zoneOffsetDecoder = Derive.derive[ValueDecoder, ZoneOffset](ValueDecoderDeriver.default) + val durationDecoder = Derive.derive[ValueDecoder, Duration](ValueDecoderDeriver.default) + val instantDecoder = Derive.derive[ValueDecoder, Instant](ValueDecoderDeriver.default) + val localDateDecoder = Derive.derive[ValueDecoder, LocalDate](ValueDecoderDeriver.default) + val localTimeDecoder = Derive.derive[ValueDecoder, LocalTime](ValueDecoderDeriver.default) + val localDateTimeDecoder = Derive.derive[ValueDecoder, LocalDateTime](ValueDecoderDeriver.default) + val offsetTimeDecoder = Derive.derive[ValueDecoder, OffsetTime](ValueDecoderDeriver.default) + val offsetDateTimeDecoder = Derive.derive[ValueDecoder, OffsetDateTime](ValueDecoderDeriver.default) + val zonedDateTimeDecoder = Derive.derive[ValueDecoder, ZonedDateTime](ValueDecoderDeriver.default) + + val stringPayload = "foo" + val booleanPayload = false + val bytePayload = 10.toByte + val shortPayload = 30.toShort + val intPayload = 254 + val longPayload = 398812L + val floatPayload = 23.123f + val doublePayload = 0.1231454123d + val binaryPayload = Chunk.fromArray("foobar".getBytes) + val charPayload = 'c' + val uuidPayload = UUID.randomUUID() + val bigDecimalPayload = new BigDecimal("993123312.32") + val bigIntegerPayload = new BigInteger("99931234123") + val dayOfWeekPayload = DayOfWeek.of(5) + val monthPayload = Month.of(12) + val monthDayPayload = MonthDay.of(10, 23) + val periodPayload = Period.of(9, 1, 14) + val yearPayload = Year.of(1989) + val yearMonthPayload = YearMonth.of(1989, 5) + val zoneIdPayload = ZoneId.of("Europe/Paris") + val zoneOffsetPayload = ZoneOffset.of("+02:00") + val durationPayload = Duration.fromSeconds(99312312) + val instantPayload = Instant.ofEpochMilli(Instant.now().toEpochMilli) + val localDatePayload = LocalDate.now() + val localTimePayload = LocalTime.ofNanoOfDay((LocalTime.now().toNanoOfDay / 1000000L) * 1000000L) + val localDateTimePayload = LocalDateTime.of(localDatePayload, localTimePayload) + val offsetTimePayload = OffsetTime.of(localTimePayload, zoneOffsetPayload) + val offsetDateTimePayload = OffsetDateTime.of(localDateTimePayload, zoneOffsetPayload) + val zonedDateTimePayload = ZonedDateTime.of(localDateTimePayload, zoneIdPayload) + + val expectedOffsetTimeUTC = { + val timeNanos = offsetTimePayload.toLocalTime.toNanoOfDay + val offsetNanos = offsetTimePayload.getOffset.getTotalSeconds * NANOS_FACTOR + val dayNanos = timeNanos - offsetNanos + + OffsetTime.of(LocalTime.ofNanoOfDay(dayNanos), ZoneOffset.UTC) + } + + val expectedOffsetDateTimeUTC = { + val epochDay = offsetDateTimePayload.toLocalDate.toEpochDay + val timeNanos = offsetDateTimePayload.toLocalTime.toNanoOfDay + val offsetNanos = offsetDateTimePayload.getOffset.getTotalSeconds * NANOS_FACTOR + val timeOffsetNanos = timeNanos - offsetNanos + val nanoOfDay = if (timeOffsetNanos < 0) NANOS_PER_DAY - timeOffsetNanos else timeOffsetNanos + val epochDayOrDayBefore = if (timeOffsetNanos < 0) epochDay - 1 else epochDay + + OffsetDateTime.of( + LocalDateTime.of(LocalDate.ofEpochDay(epochDayOrDayBefore), LocalTime.ofNanoOfDay(nanoOfDay)), + ZoneOffset.UTC + ) + } + + val expectedZonedDateTimeUTC = { + val epochDay = zonedDateTimePayload.toLocalDate.toEpochDay + val timeNanos = zonedDateTimePayload.toLocalTime.toNanoOfDay + val offsetNanos = zonedDateTimePayload.getOffset.getTotalSeconds * NANOS_FACTOR + val timeOffsetNanos = timeNanos - offsetNanos + val nanoOfDay = if (timeOffsetNanos < 0) NANOS_PER_DAY - timeOffsetNanos else timeOffsetNanos + val epochDayOrDayBefore = if (timeOffsetNanos < 0) epochDay - 1 else epochDay + + ZonedDateTime.of( + LocalDateTime.of(LocalDate.ofEpochDay(epochDayOrDayBefore), LocalTime.ofNanoOfDay(nanoOfDay)), + ZoneId.of("Z") + ) + } for { - stringValue <- stringEncoder.encodeZIO(stringPayload) - stringResult <- stringDecoder.decodeZIO(stringValue) - booleanValue <- booleanEncoder.encodeZIO(booleanPayload) - booleanResult <- booleanDecoder.decodeZIO(booleanValue) - byteValue <- byteEncoder.encodeZIO(bytePayload) - byteResult <- byteDecoder.decodeZIO(byteValue) - shortValue <- shortEncoder.encodeZIO(shortPayload) - shortResult <- shortDecoder.decodeZIO(shortValue) - intValue <- intEncoder.encodeZIO(intPayload) - intResult <- intDecoder.decodeZIO(intValue) - longValue <- longEncoder.encodeZIO(longPayload) - longResult <- longDecoder.decodeZIO(longValue) - uuidValue <- uuidEncoder.encodeZIO(uuidPayload) - uuidResult <- uuidDecoder.decodeZIO(uuidValue) + stringValue <- stringEncoder.encodeZIO(stringPayload) + stringResult <- stringDecoder.decodeZIO(stringValue) + booleanValue <- booleanEncoder.encodeZIO(booleanPayload) + booleanResult <- booleanDecoder.decodeZIO(booleanValue) + byteValue <- byteEncoder.encodeZIO(bytePayload) + byteResult <- byteDecoder.decodeZIO(byteValue) + shortValue <- shortEncoder.encodeZIO(shortPayload) + shortResult <- shortDecoder.decodeZIO(shortValue) + intValue <- intEncoder.encodeZIO(intPayload) + intResult <- intDecoder.decodeZIO(intValue) + longValue <- longEncoder.encodeZIO(longPayload) + longResult <- longDecoder.decodeZIO(longValue) + floatValue <- floatEncoder.encodeZIO(floatPayload) + floatResult <- floatDecoder.decodeZIO(floatValue) + doubleValue <- doubleEncoder.encodeZIO(doublePayload) + doubleResult <- doubleDecoder.decodeZIO(doubleValue) + binaryValue <- binaryEncoder.encodeZIO(binaryPayload) + binaryResult <- binaryDecoder.decodeZIO(binaryValue) + charValue <- charEncoder.encodeZIO(charPayload) + charResult <- charDecoder.decodeZIO(charValue) + uuidValue <- uuidEncoder.encodeZIO(uuidPayload) + uuidResult <- uuidDecoder.decodeZIO(uuidValue) + bigDecimalValue <- bigDecimalEncoder.encodeZIO(bigDecimalPayload) + bigDecimalResult <- bigDecimalDecoder.decodeZIO(bigDecimalValue) + bigIntegerValue <- bigIntegerEncoder.encodeZIO(bigIntegerPayload) + bigIntegerResult <- bigIntegerDecoder.decodeZIO(bigIntegerValue) + dayOfWeekValue <- dayOfWeekEncoder.encodeZIO(dayOfWeekPayload) + dayOfWeekResult <- dayOfWeekDecoder.decodeZIO(dayOfWeekValue) + monthValue <- monthEncoder.encodeZIO(monthPayload) + monthResult <- monthDecoder.decodeZIO(monthValue) + monthDayValue <- monthDayEncoder.encodeZIO(monthDayPayload) + monthDayResult <- monthDayDecoder.decodeZIO(monthDayValue) + periodValue <- periodEncoder.encodeZIO(periodPayload) + periodResult <- periodDecoder.decodeZIO(periodValue) + yearValue <- yearEncoder.encodeZIO(yearPayload) + yearResult <- yearDecoder.decodeZIO(yearValue) + yearMonthValue <- yearMonthEncoder.encodeZIO(yearMonthPayload) + yearMonthResult <- yearMonthDecoder.decodeZIO(yearMonthValue) + zoneIdValue <- zoneIdEncoder.encodeZIO(zoneIdPayload) + zoneIdResult <- zoneIdDecoder.decodeZIO(zoneIdValue) + zoneOffsetValue <- zoneOffsetEncoder.encodeZIO(zoneOffsetPayload) + zoneOffsetResult <- zoneOffsetDecoder.decodeZIO(zoneOffsetValue) + durationValue <- durationEncoder.encodeZIO(durationPayload) + durationResult <- durationDecoder.decodeZIO(durationValue) + instantValue <- instantEncoder.encodeZIO(instantPayload) + instantResult <- instantDecoder.decodeZIO(instantValue) + localDateValue <- localDateEncoder.encodeZIO(localDatePayload) + localDateResult <- localDateDecoder.decodeZIO(localDateValue) + localTimeValue <- localTimeEncoder.encodeZIO(localTimePayload) + localTimeResult <- localTimeDecoder.decodeZIO(localTimeValue) + localDateTimeValue <- localDateTimeEncoder.encodeZIO(localDateTimePayload) + localDateTimeResult <- localDateTimeDecoder.decodeZIO(localDateTimeValue) + offsetTimeValue <- offsetTimeEncoder.encodeZIO(offsetTimePayload) + offsetTimeResult <- offsetTimeDecoder.decodeZIO(offsetTimeValue) + offsetDateTimeValue <- offsetDateTimeEncoder.encodeZIO(offsetDateTimePayload) + offsetDateTimeResult <- offsetDateTimeDecoder.decodeZIO(offsetDateTimeValue) + zonedDateTimeValue <- zonedDateTimeEncoder.encodeZIO(zonedDateTimePayload) + zonedDateTimeResult <- zonedDateTimeDecoder.decodeZIO(zonedDateTimeValue) } yield assertTrue( stringResult == stringPayload, booleanResult == booleanPayload, @@ -73,7 +248,29 @@ object ValueCodecDeriverSpec extends ZIOSpecDefault { shortResult == shortPayload, intResult == intPayload, longResult == longPayload, - uuidResult == uuidPayload + floatResult == floatPayload, + doubleResult == doublePayload, + binaryResult == binaryPayload, + charResult == charPayload, + uuidResult == uuidPayload, + bigDecimalResult == bigDecimalPayload, + bigIntegerResult == bigIntegerPayload, + dayOfWeekResult == dayOfWeekPayload, + monthResult == monthPayload, + monthDayResult == monthDayPayload, + periodResult == periodPayload, + yearResult == yearPayload, + yearMonthResult == yearMonthPayload, + zoneIdResult == zoneIdPayload, + zoneOffsetResult == zoneOffsetPayload, + durationResult == durationPayload, + instantResult == instantPayload, + localDateResult == localDatePayload, + localTimeResult == localTimePayload, + localDateTimeResult == localDateTimePayload, + offsetTimeResult == expectedOffsetTimeUTC, + offsetDateTimeResult == expectedOffsetDateTimeUTC, + zonedDateTimeResult == expectedZonedDateTimeUTC ) }, test("option") { @@ -121,6 +318,19 @@ object ValueCodecDeriverSpec extends ZIOSpecDefault { value <- encoder.encodeZIO(payload) result <- decoder.decodeZIO(value) } yield assertTrue(result == payload) + }, + test("enum") { + val encoder = Derive.derive[ValueEncoder, MyEnum](ValueEncoderDeriver.default) + val decoder = Derive.derive[ValueDecoder, MyEnum](ValueDecoderDeriver.default) + val payload = MyEnum.Started + val wrongId = Value.string("Gone") + + for { + value <- encoder.encodeZIO(payload) + result <- decoder.decodeZIO(value) + wrongResult <- decoder.decodeZIO(wrongId).either + } yield assertTrue(result == payload) && + assert(wrongResult)(isLeft(isSubtype[DecoderError](anything))) } // test("summoned") { // @nowarn