forked from zio/zio-schema
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit of wip from https://github.com/zio/zio-web/pull/90. zio#7
> Co-authored-by: Brandon Brown <brandon@bbrownsound.com> Co-authored-by: Jason Pickens <jasonpickensnz@gmail.com>
- Loading branch information
Showing
13 changed files
with
826 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package zio.schema.codec | ||
|
||
import zio.json.{ JsonDecoder, JsonEncoder } | ||
import zio.schema.Schema | ||
|
||
trait Codec { | ||
def encoder[A](schema: Schema[A]): JsonEncoder[A] | ||
def decoder[A](schema: Schema[A]): JsonDecoder[A] | ||
} |
43 changes: 43 additions & 0 deletions
43
core/src/main/scala/zio/schema/codec/json/CodecDecoder.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package zio.schema.codec.json | ||
|
||
import java.time.DayOfWeek | ||
import zio.json.JsonDecoder | ||
import zio.json.internal.RetractReader | ||
import zio.schema.StandardType | ||
|
||
object CodecDecoder { | ||
|
||
final def primitiveDecoder[A](standardType: StandardType[A]): JsonDecoder[A] = | ||
standardType match { | ||
case StandardType.UnitType => unitDecoder | ||
case StandardType.StringType => JsonDecoder[String] | ||
case StandardType.BoolType => JsonDecoder[Boolean] | ||
case StandardType.ShortType => JsonDecoder[Short] | ||
case StandardType.IntType => JsonDecoder[Int] | ||
case StandardType.LongType => JsonDecoder[Long] | ||
case StandardType.FloatType => JsonDecoder[Float] | ||
case StandardType.DoubleType => JsonDecoder[Double] | ||
case StandardType.ByteType => JsonDecoder[Byte] | ||
case StandardType.CharType => JsonDecoder[Char] | ||
case StandardType.DayOfWeekType => JsonDecoder[DayOfWeek] | ||
case StandardType.Duration(_) => JsonDecoder[java.time.Duration] | ||
case StandardType.Instant(_) => JsonDecoder[java.time.Instant] | ||
case StandardType.LocalDate(_) => JsonDecoder[java.time.LocalDate] | ||
case StandardType.LocalDateTime(_) => JsonDecoder[java.time.LocalDateTime] | ||
case StandardType.LocalTime(_) => JsonDecoder[java.time.LocalTime] | ||
case StandardType.Month => JsonDecoder[java.time.Month] | ||
case StandardType.MonthDay => JsonDecoder[java.time.MonthDay] | ||
case StandardType.OffsetDateTime(_) => JsonDecoder[java.time.OffsetDateTime] | ||
case StandardType.OffsetTime(_) => JsonDecoder[java.time.OffsetTime] | ||
case StandardType.Period => JsonDecoder[java.time.Period] | ||
case StandardType.Year => JsonDecoder[java.time.Year] | ||
case StandardType.YearMonth => JsonDecoder[java.time.YearMonth] | ||
case StandardType.ZonedDateTime(_) => JsonDecoder[java.time.ZonedDateTime] | ||
case StandardType.ZoneId => JsonDecoder[java.time.ZoneId] | ||
case StandardType.ZoneOffset => JsonDecoder[java.time.ZoneOffset] | ||
} | ||
|
||
private val unitDecoder: JsonDecoder[Unit] = | ||
(_: List[JsonDecoder.JsonError], _: RetractReader) => () | ||
|
||
} |
88 changes: 88 additions & 0 deletions
88
core/src/main/scala/zio/schema/codec/json/CodecEncoder.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package zio.schema.codec.json | ||
|
||
import zio.json.JsonEncoder.string | ||
import zio.json.internal.Write | ||
import zio.json.{ JsonEncoder, JsonFieldEncoder } | ||
import zio.schema.StandardType | ||
|
||
object CodecEncoder { | ||
|
||
final def primitiveEncoder[A](standardType: StandardType[A]): JsonEncoder[A] = | ||
standardType match { | ||
case StandardType.UnitType => unitEncoder | ||
case StandardType.StringType => JsonEncoder[String] | ||
case StandardType.BoolType => JsonEncoder[Boolean] | ||
case StandardType.ShortType => JsonEncoder[Short] | ||
case StandardType.IntType => JsonEncoder[Int] | ||
case StandardType.LongType => JsonEncoder[Long] | ||
case StandardType.FloatType => JsonEncoder[Float] | ||
case StandardType.DoubleType => JsonEncoder[Double] | ||
case StandardType.ByteType => JsonEncoder[Byte] | ||
case StandardType.CharType => JsonEncoder[Char] | ||
case StandardType.DayOfWeekType => JsonEncoder[java.time.DayOfWeek] | ||
case StandardType.Duration(_) => JsonEncoder[java.time.Duration] | ||
case StandardType.Instant(_) => JsonEncoder[java.time.Instant] | ||
case StandardType.LocalDate(_) => JsonEncoder[java.time.LocalDate] | ||
case StandardType.LocalDateTime(_) => JsonEncoder[java.time.LocalDateTime] | ||
case StandardType.LocalTime(_) => JsonEncoder[java.time.LocalTime] | ||
case StandardType.Month => JsonEncoder[java.time.Month] | ||
case StandardType.MonthDay => JsonEncoder[java.time.MonthDay] | ||
case StandardType.OffsetDateTime(_) => JsonEncoder[java.time.OffsetDateTime] | ||
case StandardType.OffsetTime(_) => JsonEncoder[java.time.OffsetTime] | ||
case StandardType.Period => JsonEncoder[java.time.Period] | ||
case StandardType.Year => JsonEncoder[java.time.Year] | ||
case StandardType.YearMonth => JsonEncoder[java.time.YearMonth] | ||
case StandardType.ZonedDateTime(_) => JsonEncoder[java.time.ZonedDateTime] | ||
case StandardType.ZoneId => JsonEncoder[java.time.ZoneId] | ||
case StandardType.ZoneOffset => JsonEncoder[java.time.ZoneOffset] | ||
} | ||
|
||
private val unitEncoder: JsonEncoder[Unit] = | ||
(_: Unit, _: Option[Int], _: Write) => () | ||
|
||
// A modified version of zio.json.EncoderLowPriority2.keyValueIterable. | ||
final def recordEncoder[A](keyValueEncoders: Map[String, JsonEncoder[_]]): JsonEncoder[A] = new JsonEncoder[A] { | ||
|
||
def unsafeEncode(record: A, indent: Option[Int], out: Write): Unit = { | ||
if (keyValueEncoders.isEmpty) return out.write("{}") | ||
|
||
val K = JsonFieldEncoder.string | ||
|
||
// FIXME: Can we do this safely? | ||
val product = record.asInstanceOf[Product] | ||
val elements = for (i <- 0 until product.productArity) yield { | ||
val name = product.productElementName(i) | ||
val value = product.productElement(i) | ||
name -> value | ||
} | ||
|
||
out.write('{') | ||
val indent_ = JsonEncoder.bump(indent) | ||
JsonEncoder.pad(indent_, out) | ||
var first = true | ||
elements.foreach { | ||
case (k, a) => | ||
// FIXME: Can we do this safely? | ||
val A = keyValueEncoders.getOrElse(k, throw new Exception(s"No Schema found for field '$k'.")) | ||
// FIXME: Can we do this safely? | ||
if (!A.isNothing(a.asInstanceOf)) { | ||
if (first) | ||
first = false | ||
else { | ||
out.write(',') | ||
if (!indent.isEmpty) | ||
JsonEncoder.pad(indent_, out) | ||
} | ||
|
||
string.unsafeEncode(K.unsafeEncodeField(k), indent_, out) | ||
if (indent.isEmpty) out.write(':') | ||
else out.write(" : ") | ||
// FIXME: Can we do this safely? | ||
A.unsafeEncode(a.asInstanceOf, indent_, out) | ||
} | ||
} | ||
JsonEncoder.pad(indent, out) | ||
out.write('}') | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package zio.schema.codec.json | ||
|
||
import zio.json.{ JsonDecoder, JsonEncoder } | ||
import zio.schema._ | ||
import zio.schema.codec.Codec | ||
|
||
// TODO: Should this be a class that takes a character encoding parameter? | ||
object JsonCodec extends Codec { | ||
|
||
override def encoder[A](schema: Schema[A]): JsonEncoder[A] = schema match { | ||
case Schema.Record(structure) => | ||
val keyValueEncoders = structure.view.mapValues(encoder(_)).toMap | ||
CodecEncoder.recordEncoder(keyValueEncoders) | ||
case Schema.Sequence(element) => JsonEncoder.chunk(encoder(element)) | ||
case Schema.Enumeration(enumeration) => CodecEncoder.recordEncoder(enumeration.view.mapValues(encoder(_)).toMap) | ||
case Schema.Transform(_, _, _) => ??? | ||
case Schema.Primitive(standardType) => CodecEncoder.primitiveEncoder(standardType) | ||
case Schema.Tuple(left, right) => JsonEncoder.tuple2(encoder(left), encoder(right)) | ||
case Schema.Optional(schema) => JsonEncoder.option(encoder(schema)) | ||
} | ||
|
||
override def decoder[A](schema: Schema[A]): JsonDecoder[A] = schema match { | ||
case Schema.Record(_) => ??? | ||
case Schema.Sequence(element) => JsonDecoder.chunk(decoder(element)) | ||
case Schema.Enumeration(_) => ??? | ||
case Schema.Transform(_, _, _) => ??? | ||
case Schema.Primitive(standardType) => CodecDecoder.primitiveDecoder(standardType) | ||
case Schema.Tuple(left, right) => JsonDecoder.tuple2(decoder(left), decoder(right)) | ||
case Schema.Optional(schema) => JsonDecoder.option(decoder(schema)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,4 +106,4 @@ object DeriveSchema { | |
) | ||
|
||
implicit def gen[T]: Schema[T] = macro Magnolia.gen[T] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package zio.schema | ||
|
||
import java.time._ | ||
import java.time.temporal.ChronoField | ||
import scala.jdk.CollectionConverters._ | ||
import zio.ZIO | ||
import zio.random.Random | ||
import zio.stream.ZStream | ||
import zio.test.{ Gen, Sample } | ||
|
||
object JavaTimeGen { | ||
|
||
val anyDayOfWeek: Gen[Random, DayOfWeek] = Gen.oneOf( | ||
Gen.const(DayOfWeek.MONDAY), | ||
Gen.const(DayOfWeek.TUESDAY), | ||
Gen.const(DayOfWeek.WEDNESDAY), | ||
Gen.const(DayOfWeek.THURSDAY), | ||
Gen.const(DayOfWeek.FRIDAY), | ||
Gen.const(DayOfWeek.SATURDAY), | ||
Gen.const(DayOfWeek.SUNDAY) | ||
) | ||
|
||
val anyMonth: Gen[Random, Month] = Gen.oneOf( | ||
Gen.const(Month.JANUARY), | ||
Gen.const(Month.FEBRUARY), | ||
Gen.const(Month.MARCH), | ||
Gen.const(Month.APRIL), | ||
Gen.const(Month.MAY), | ||
Gen.const(Month.JUNE), | ||
Gen.const(Month.JULY), | ||
Gen.const(Month.AUGUST), | ||
Gen.const(Month.SEPTEMBER), | ||
Gen.const(Month.OCTOBER), | ||
Gen.const(Month.NOVEMBER), | ||
Gen.const(Month.DECEMBER) | ||
) | ||
|
||
val anyNanoOfDay: Gen[Random, Long] = chronoFieldValue(ChronoField.NANO_OF_DAY) | ||
|
||
val anyEpochDay: Gen[Random, Long] = chronoFieldValue(ChronoField.EPOCH_DAY) | ||
|
||
val anyMonthOfYear: Gen[Random, Int] = chronoFieldValue(ChronoField.MONTH_OF_YEAR).map(_.toInt) | ||
|
||
val anyMonthDay: Gen[Random, MonthDay] = | ||
for { | ||
month <- anyMonth | ||
dayOfMonth <- Gen.int(1, month.maxLength) | ||
} yield MonthDay.of(month, dayOfMonth) | ||
|
||
val anyIntYear: Gen[Random, Int] = chronoFieldValue(ChronoField.YEAR).map(_.toInt) | ||
|
||
val anyYear: Gen[Random, Year] = anyIntYear.map(Year.of) | ||
|
||
val anyYearMonth: Gen[Random, YearMonth] = | ||
anyIntYear.zipWith(anyMonthOfYear) { (year, month) => | ||
YearMonth.of(year, month) | ||
} | ||
|
||
private def chronoFieldValue(chronoField: ChronoField) = { | ||
val range = chronoField.range | ||
Gen.long(range.getMinimum, range.getMaximum) | ||
} | ||
|
||
val anyDuration: Gen[Random, Duration] = Gen.anyLong.zipWith(Gen.long(0, 999_999_999L)) { (seconds, nanos) => | ||
Duration.ofSeconds(seconds, nanos) | ||
} | ||
|
||
val anyPeriod: Gen[Random, Period] = | ||
for { | ||
years <- Gen.anyInt | ||
months <- Gen.anyInt | ||
days <- Gen.anyInt | ||
} yield Period.of(years, months, days) | ||
|
||
val anyInstant: Gen[Random, Instant] = Gen | ||
.long(Instant.MIN.getEpochSecond, Instant.MAX.getEpochSecond) | ||
.zipWith(Gen.int(Instant.MIN.getNano, Instant.MAX.getNano)) { (seconds, nanos) => | ||
Instant.ofEpochSecond(seconds, nanos.toLong) | ||
} | ||
|
||
val anyLocalDate: Gen[Random, LocalDate] = anyEpochDay.map(LocalDate.ofEpochDay) | ||
|
||
val anyLocalTime: Gen[Random, LocalTime] = anyNanoOfDay.map(LocalTime.ofNanoOfDay) | ||
|
||
val anyLocalDateTime: Gen[Random, LocalDateTime] = anyLocalDate.zipWith(anyLocalTime) { (date, time) => | ||
LocalDateTime.of(date, time) | ||
} | ||
|
||
val anyZoneOffset: Gen[Random, ZoneOffset] = | ||
Gen.int(ZoneOffset.MIN.getTotalSeconds, ZoneOffset.MAX.getTotalSeconds).map(ZoneOffset.ofTotalSeconds) | ||
|
||
// This uses ZoneRulesProvider which has an effectful static initializer. | ||
private val regionZoneIds = | ||
ZIO.succeed(ZoneId.getAvailableZoneIds.asScala.toSet.map(ZoneId.of)) | ||
|
||
private val zoneOffsets = | ||
(ZoneOffset.MIN.getTotalSeconds to ZoneOffset.MAX.getTotalSeconds).map(ZoneOffset.ofTotalSeconds) | ||
|
||
private val zoneIds = regionZoneIds.map(_.toList ++ zoneOffsets) | ||
|
||
// FIXME: Shuffle is really slow. | ||
//private val zoneIds = | ||
// for { | ||
// ids <- regionZoneIds | ||
// all = ids ++ zoneOffsets | ||
// random <- ZIO.service[Random.Service] | ||
// shuffled <- random.shuffle(all.toList) | ||
// } yield shuffled | ||
|
||
val anyZoneId: Gen[Random, ZoneId] = | ||
Gen(ZStream.fromIterableM(zoneIds).map { | ||
case offset: ZoneOffset => Sample.noShrink(offset) | ||
// FIXME: This is really slow even when it isn't shrinking. | ||
//Sample.shrinkIntegral(ZoneOffset.UTC.getTotalSeconds)(offset.getTotalSeconds).map { seconds => | ||
// ZoneOffset.ofTotalSeconds(seconds) | ||
//} | ||
case zone => Sample.noShrink(zone) | ||
}) | ||
|
||
// TODO: This needs to be double checked. I have encountered problems generating these in the past. | ||
// See https://github.com/BotTech/scala-hedgehog-spines/blob/master/core/src/main/scala/com/lightbend/hedgehog/generators/time/TimeGenerators.scala | ||
val anyZonedDateTime: Gen[Random, ZonedDateTime] = anyLocalDateTime.zipWith(anyZoneId) { (dateTime, zone) => | ||
ZonedDateTime.of(dateTime, zone) | ||
} | ||
|
||
val anyOffsetTime: Gen[Random, OffsetTime] = anyLocalTime.zipWith(anyZoneOffset) { (time, offset) => | ||
OffsetTime.of(time, offset) | ||
} | ||
|
||
val anyOffsetDateTime: Gen[Random, OffsetDateTime] = anyLocalDateTime.zipWith(anyZoneOffset) { (dateTime, offset) => | ||
OffsetDateTime.of(dateTime, offset) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.