Skip to content

Commit

Permalink
Schema sequence (#54)
Browse files Browse the repository at this point in the history
* Change Sequence case class and use it to construct Schema[List] and Schema[Chunk]

* Cleaning up comments etc

Co-authored-by: John A. De Goes <john@degoes.net>
  • Loading branch information
ashprakasan and jdegoes committed May 3, 2021
1 parent 3b21abd commit 71e2f0b
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 49 deletions.
17 changes: 10 additions & 7 deletions core/src/main/scala/zio/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ object Schema {

sealed case class Record(structure: Map[String, Schema[_]]) extends Schema[Map[String, _]]

sealed case class Sequence[A](element: Schema[A]) extends Schema[Chunk[A]]
final case class Sequence[Col[_], A](schemaA: Schema[A], fromChunk: Chunk[A] => Col[A], toChunk: Col[A] => Chunk[A])
extends Schema[Col[A]]

sealed case class Enumeration(structure: Map[String, Schema[_]]) extends Schema[Map[String, _]]

Expand All @@ -39,8 +40,6 @@ object Schema {

final case class EitherSchema[A, B](left: Schema[A], right: Schema[B]) extends Schema[Either[A, B]]

final case class ListSchema[A](codec: Schema[A]) extends Schema[List[A]]

sealed trait CaseClass[Z] extends Schema[Z] {
def toRecord: Record
}
Expand Down Expand Up @@ -741,8 +740,15 @@ object Schema {
}
)

implicit def schemaList[A](implicit schemaA: Schema[A]): Schema[List[A]] =
Schema.Sequence(schemaA, _.toList, Chunk.fromIterable(_))

implicit def schemaChunk[A](implicit schemaA: Schema[A]): Schema[Chunk[A]] =
Schema.Sequence(schemaA, identity, identity)

implicit def leftSchema[A, B](implicit schemaA: Schema[A]): Schema[Left[A, Nothing]] =
schemaA.transform(Left(_), _.value)

implicit def rightSchema[A, B](implicit schemaB: Schema[B]): Schema[Right[Nothing, B]] =
schemaB.transform(Right(_), _.value)

Expand All @@ -758,9 +764,6 @@ object Schema {
def first[A](codec: Schema[(A, Unit)]): Schema[A] =
codec.transform[A](_._1, a => (a, ()))

implicit def list[A](implicit element: Schema[A]): Schema[List[A]] =
sequence(element).transform(_.toList, Chunk.fromIterable(_))

implicit def option[A](implicit element: Schema[A]): Schema[Option[A]] =
Optional(element)

Expand All @@ -771,7 +774,7 @@ object Schema {
Record(structure)

implicit def sequence[A](implicit element: Schema[A]): Schema[Chunk[A]] =
Sequence(element)
Sequence(element, (c: Chunk[A]) => c, (c: Chunk[A]) => c)

implicit def set[A](implicit element: Schema[A]): Schema[Set[A]] =
sequence(element).transform(_.toSet, Chunk.fromIterable(_))
Expand Down
19 changes: 10 additions & 9 deletions core/src/main/scala/zio/schema/codec/JsonCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import zio.json.JsonCodec._
import zio.json.JsonDecoder.{ JsonError, UnsafeJson }
import zio.json.internal.{ Lexer, RetractReader, StringMatrix, Write }
import zio.json.{ JsonCodec => ZJsonCodec, JsonDecoder, JsonEncoder, JsonFieldDecoder, JsonFieldEncoder }
import zio.schema.Schema.{ EitherSchema, ListSchema }
import zio.schema.Schema.EitherSchema
import zio.schema.{ StandardType, _ }
import zio.stream.ZTransducer
import zio.{ Chunk, ChunkBuilder, ZIO }
Expand Down Expand Up @@ -37,7 +37,8 @@ object JsonCodec extends Codec {
object Codecs {
protected[codec] val unitEncoder: JsonEncoder[Unit] = new JsonEncoder[Unit] {
override def unsafeEncode(a: Unit, indent: Option[Int], out: Write): Unit = ()
override def isNothing(a: Unit): Boolean = true

override def isNothing(a: Unit): Boolean = true
}
protected[codec] val unitDecoder: JsonDecoder[Unit] =
(_: List[JsonDecoder.JsonError], _: RetractReader) => ()
Expand Down Expand Up @@ -81,6 +82,7 @@ object JsonCodec extends Codec {
}

object Encoder {

import Codecs._
import JsonEncoder.{ bump, pad }

Expand All @@ -96,15 +98,14 @@ object JsonCodec extends Codec {

private def schemaEncoder[A](schema: Schema[A]): JsonEncoder[A] = schema match {
case Schema.Primitive(standardType) => primitiveCodec(standardType)
case Schema.Sequence(schema) => JsonEncoder.chunk(schemaEncoder(schema))
case Schema.Sequence(schema, _, g) => JsonEncoder.chunk(schemaEncoder(schema)).contramap(g)
case Schema.Transform(c, _, g) => transformEncoder(c, g)
case Schema.Tuple(l, r) => schemaEncoder(l).both(schemaEncoder(r))
case Schema.Optional(schema) => JsonEncoder.option(schemaEncoder(schema))
case Schema.Fail(_) => unitEncoder.contramap(_ => ())
case Schema.Record(structure) => recordEncoder(structure)
case Schema.Enumeration(structure) => enumEncoder(structure)
case EitherSchema(left, right) => JsonEncoder.either(schemaEncoder(left), schemaEncoder(right))
case ListSchema(codec) => JsonEncoder.list(schemaEncoder(codec))
case Schema.CaseClass1(f, _, ext) => caseClassEncoder(f -> ext)
case Schema.CaseClass2(f1, f2, _, ext1, ext2) => caseClassEncoder(f1 -> ext1, f2 -> ext2)
case Schema.CaseClass3(f1, f2, f3, _, ext1, ext2, ext3) => caseClassEncoder(f1 -> ext1, f2 -> ext2, f3 -> ext3)
Expand Down Expand Up @@ -820,7 +821,6 @@ object JsonCodec extends Codec {
f21 -> ext21,
f22 -> ext22
)

}

private def transformEncoder[A, B](schema: Schema[A], g: B => Either[String, A]): JsonEncoder[B] = {
Expand Down Expand Up @@ -879,7 +879,6 @@ object JsonCodec extends Codec {
if (indent.isDefined)
JsonEncoder.pad(indent_, out)
}

string.unsafeEncode(JsonFieldEncoder.string.unsafeEncodeField(k), indent_, out)
if (indent.isEmpty) out.write(':')
else out.write(" : ")
Expand All @@ -888,7 +887,6 @@ object JsonCodec extends Codec {
pad(indent, out)
out.write('}')
}

}
}

Expand Down Expand Up @@ -921,10 +919,13 @@ object JsonCodec extends Codec {
}
}
}

}

object Decoder {

import Codecs._

final def decode[A](schema: Schema[A], json: String): Either[String, A] =
schemaDecoder(schema).decodeJson(json)

Expand All @@ -933,12 +934,11 @@ object JsonCodec extends Codec {
case Schema.Optional(codec) => JsonDecoder.option(schemaDecoder(codec))
case Schema.Tuple(left, right) => JsonDecoder.tuple2(schemaDecoder(left), schemaDecoder(right))
case Schema.Transform(codec, f, _) => schemaDecoder(codec).mapOrFail(f)
case Schema.Sequence(codec) => JsonDecoder.chunk(schemaDecoder(codec))
case Schema.Sequence(codec, f, _) => JsonDecoder.chunk(schemaDecoder(codec)).map(f)
case Schema.Fail(message) => failDecoder(message)
case Schema.Record(structure) => recordDecoder(structure)
case Schema.Enumeration(structure) => enumDecoder(structure)
case EitherSchema(left, right) => JsonDecoder.either(schemaDecoder(left), schemaDecoder(right))
case Schema.ListSchema(codec) => JsonDecoder.list(schemaDecoder(codec))
case s @ Schema.CaseClass1(_, _, _) => caseClass1Decoder(s)
case s @ Schema.CaseClass2(_, _, _, _, _) => caseClass2Decoder(s)
case s @ Schema.CaseClass3(_, _, _, _, _, _, _) => caseClass3Decoder(s)
Expand Down Expand Up @@ -2256,4 +2256,5 @@ object JsonCodec extends Codec {
}
}
}

}
37 changes: 21 additions & 16 deletions core/src/main/scala/zio/schema/codec/ProtobufCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,19 @@ object ProtobufCodec extends Codec {
sealed trait WireType

object WireType {
case object VarInt extends WireType
case object Bit64 extends WireType

case object VarInt extends WireType

case object Bit64 extends WireType

case class LengthDelimited(width: Int) extends WireType
case object StartGroup extends WireType
case object EndGroup extends WireType
case object Bit32 extends WireType

case object StartGroup extends WireType

case object EndGroup extends WireType

case object Bit32 extends WireType

}

def flatFields(
Expand Down Expand Up @@ -99,15 +106,14 @@ object ProtobufCodec extends Codec {
@scala.annotation.tailrec
def canBePacked(schema: Schema[_]): Boolean = schema match {
case _: Schema.Record => false
case Schema.Sequence(element) => canBePacked(element)
case Schema.Sequence(element, _, _) => canBePacked(element)
case _: Schema.Enumeration => false
case Schema.Transform(codec, _, _) => canBePacked(codec)
case Schema.Primitive(standardType) => canBePacked(standardType)
case _: Schema.Tuple[_, _] => false
case _: Schema.Optional[_] => false
case _: Schema.Fail[_] => false
case _: Schema.EitherSchema[_, _] => false
case _: Schema.ListSchema[_] => false
case _: Schema.CaseClass[_] => false
}

Expand Down Expand Up @@ -144,12 +150,13 @@ object ProtobufCodec extends Codec {
}

object Encoder {

import Protobuf._

def encode[A](fieldNumber: Option[Int], schema: Schema[A], value: A): Chunk[Byte] =
(schema, value) match {
case (Schema.Record(structure), v: Map[String, _]) => encodeRecord(fieldNumber, structure, v)
case (Schema.Sequence(element), v: Chunk[_]) => encodeSequence(fieldNumber, element, v)
case (Schema.Sequence(element, _, g), v) => encodeSequence(fieldNumber, element, g(v))
case (Schema.Enumeration(structure), v: Map[String, _]) => encodeEnumeration(fieldNumber, structure, v)
case (Schema.Transform(codec, _, g), _) => g(value).map(encode(fieldNumber, codec, _)).getOrElse(Chunk.empty)
case (Schema.Primitive(standardType), v) => encodePrimitive(fieldNumber, standardType, v)
Expand Down Expand Up @@ -1200,7 +1207,8 @@ object ProtobufCodec extends Codec {
}.getOrElse(Chunk.empty)
}

final case class Decoder[+A](run: Chunk[Byte] => Either[String, (Chunk[Byte], A)]) { self =>
final case class Decoder[+A](run: Chunk[Byte] => Either[String, (Chunk[Byte], A)]) {
self =>

def map[B](f: A => B): Decoder[B] =
Decoder(
Expand Down Expand Up @@ -1248,6 +1256,7 @@ object ProtobufCodec extends Codec {
}

object Decoder {

import Protobuf._

def fail(failure: String): Decoder[Nothing] = Decoder(_ => Left(failure))
Expand All @@ -1269,16 +1278,15 @@ object ProtobufCodec extends Codec {
private def decoder[A](schema: Schema[A]): Decoder[A] =
schema match {
case Schema.Record(structure) => recordDecoder(flatFields(structure))
case Schema.Sequence(element) =>
if (canBePacked(element)) packedSequenceDecoder(element) else nonPackedSequenceDecoder(element)
case Schema.Sequence(element, f, _) =>
if (canBePacked(element)) packedSequenceDecoder(element).map(f) else nonPackedSequenceDecoder(element).map(f)
case Schema.Enumeration(structure) => enumDecoder(flatFields(structure))
case Schema.Transform(codec, f, _) => transformDecoder(codec, f)
case Schema.Primitive(standardType) => primitiveDecoder(standardType)
case Schema.Tuple(left, right) => tupleDecoder(left, right)
case Schema.Optional(codec) => optionalDecoder(codec)
case Schema.Fail(message) => fail(message)
case Schema.EitherSchema(left, right) => eitherDecoder(left, right)
case Schema.ListSchema(codec) => listDecoder(codec)
case s: Schema.CaseClass1[_, A] => caseClass1Decoder(s)
case s: Schema.CaseClass2[_, _, A] => caseClass2Decoder(s)
case s: Schema.CaseClass3[_, _, _, A] => caseClass3Decoder(s)
Expand Down Expand Up @@ -2314,10 +2322,6 @@ object ProtobufCodec extends Codec {
decoder(singleSchema(schema))
.map(record => record.get("value").asInstanceOf[Option[A]])

private def listDecoder[A](codec: Schema[A]): Decoder[List[A]] =
decoder(singleSchema(codec))
.map(record => record.get("value").asInstanceOf[List[A]])

private def floatDecoder: Decoder[Float] =
Decoder(bytes => {
if (bytes.size < 4) {
Expand Down Expand Up @@ -2462,4 +2466,5 @@ object ProtobufCodec extends Codec {
}
)
}

}
2 changes: 1 addition & 1 deletion core/src/test/scala/zio/schema/SchemaAssertions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object SchemaAssertions {
val right: Schema[Any] = structure2(keyAndSchema._1).asInstanceOf[Schema[Any]]
equalsSchema(left, right)
}
case (Schema.Sequence(element1), Schema.Sequence(element2)) => equalsSchema(element1, element2)
case (Schema.Sequence(element1, _, _), Schema.Sequence(element2, _, _)) => equalsSchema(element1, element2)
case (Schema.Enumeration(structure1), Schema.Enumeration(structure2)) =>
hasSameKeys(structure1, structure2) &&
structure1.forall { keyAndSchema =>
Expand Down
26 changes: 13 additions & 13 deletions core/src/test/scala/zio/schema/SchemaGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,18 @@ object SchemaGen {
(valueA, valueB) <- gen
} yield schema -> ((valueA, valueB))

val anySequence: Gen[Random with Sized, Schema.Sequence[_]] =
anySchema.map(Schema.Sequence(_))
val anySequence: Gen[Random with Sized, Schema[Chunk[Any]]] =
anySchema.map(Schema.schemaChunk(_).asInstanceOf[Schema[Chunk[Any]]])

type SequenceAndGen[A] = (Schema.Sequence[A], Gen[Random with Sized, Chunk[A]])
type SequenceAndGen[A] = (Schema[Chunk[A]], Gen[Random with Sized, Chunk[A]])

val anySequenceAndGen: Gen[Random with Sized, SequenceAndGen[_]] =
anyPrimitiveAndGen.map {
case (schema, gen) =>
Schema.Sequence(schema) -> Gen.chunkOf(gen)
Schema.schemaChunk(schema) -> Gen.chunkOf(gen)
}

type SequenceAndValue[A] = (Schema.Sequence[A], Chunk[A])
type SequenceAndValue[A] = (Schema[Chunk[A]], Chunk[A])

val anySequenceAndValue: Gen[Random with Sized, SequenceAndValue[_]] =
for {
Expand Down Expand Up @@ -189,11 +189,11 @@ object SchemaGen {
val anySequenceTransformAndGen: Gen[Random with Sized, SequenceTransformAndGen[_]] =
anyPrimitiveAndGen.map {
case (schema, gen) =>
transformSequence(Schema.Sequence(schema)) -> Gen.listOf(gen)
transformSequence(Schema.schemaChunk(schema)) -> Gen.listOf(gen)
}

// TODO: Add some random Left values.
private def transformSequence[A](schema: Schema.Sequence[A]): SequenceTransform[A] =
private def transformSequence[A](schema: Schema[Chunk[A]]): SequenceTransform[A] =
Schema.Transform[Chunk[A], List[A]](schema, chunk => Right(chunk.toList), list => Right(Chunk.fromIterable(list)))

type SequenceTransformAndValue[A] = (SequenceTransform[A], List[A])
Expand All @@ -215,9 +215,9 @@ object SchemaGen {
// TODO: How do we generate a value of a type that we know nothing about?
val anyRecordTransformAndGen: Gen[Random with Sized, RecordTransformAndGen[_]] =
Gen.empty
// anyRecordAndGen.map {
// case (schema, gen) => transformRecord(schema) -> gen
// }
// anyRecordAndGen.map {
// case (schema, gen) => transformRecord(schema) -> gen
// }

// TODO: Dynamically generate a case class.
def transformRecord[A](schema: Schema.Record): RecordTransform[A] =
Expand All @@ -242,9 +242,9 @@ object SchemaGen {
// TODO: How do we generate a value of a type that we know nothing about?
val anyEnumerationTransformAndGen: Gen[Random with Sized, EnumerationTransformAndGen[_]] =
Gen.empty
// anyEnumerationAndGen.map {
// case (schema, gen) => transformEnumeration(schema) -> gen
// }
// anyEnumerationAndGen.map {
// case (schema, gen) => transformEnumeration(schema) -> gen
// }

// TODO: Dynamically generate a sealed trait and case/value classes.
def transformEnumeration[A](schema: Schema.Enumeration): EnumerationTransform[_] =
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/scala/zio/schema/SchemaSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object SchemaSpec extends DefaultRunnableSpec {
assert(schemaUnit)(equalTo(schemaUnit))
},
test("sequence") {
assert(Schema.Sequence(schemaUnit))(equalTo(Schema.Sequence(schemaUnit)))
assert(Schema.schemaChunk(schemaUnit))(equalTo(Schema.schemaChunk(schemaUnit)))
},
test("tuple") {
assert(Schema.Tuple(schemaUnit, schemaUnit))(equalTo(Schema.Tuple(schemaUnit, schemaUnit)))
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/scala/zio/schema/codec/JsonCodecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object JsonCodecSpec extends DefaultRunnableSpec {
suite("sequence")(
testM("of primitives") {
assertEncodesJson(
Schema.Sequence(Schema.Primitive(StandardType.StringType)),
Schema.schemaChunk(Schema.Primitive(StandardType.StringType)),
Chunk("a", "b", "c")
)
}
Expand Down Expand Up @@ -274,7 +274,7 @@ object JsonCodecSpec extends DefaultRunnableSpec {
//FIXME test independently because including ZoneOffset in StandardTypeGen.anyStandardType wreaks havoc.
checkM(Gen.chunkOf(JavaTimeGen.anyZoneOffset)) { chunk =>
assertEncodesThenDecodes(
Schema.Sequence(Schema.Primitive(StandardType.ZoneOffset)),
Schema.schemaChunk(Schema.Primitive(StandardType.ZoneOffset)),
chunk
)
}
Expand Down

0 comments on commit 71e2f0b

Please sign in to comment.