Skip to content

Commit

Permalink
Support for annotations on all types (#147)
Browse files Browse the repository at this point in the history
* Add addonation to Schema[A] interface and add as field in Schema.Sequence

* Add annotation field to Schema.Transform

* Add annotation field to Schema.Primitive

* Add annotations field to Schema.Optional

* Add annotations field to Schema.Fail

* Add annotations field to Schema.Tuple

* Add annotations field to EitherSchema

* Add annotations field to Schema.Lazy

* Add annotations field to Schema.Meta

* Add remaining assertion equality checks to equalsSchema

* Remove annotations from Record[A] (since it exists in Schema[A] already) and add annotations field to Schema.GenericRecord

* Fix test case for schema generation on annotated ADT

* Fix handling of annotations for Lazy (and thus failing tests)

* Wherever we accept an annotations chunk in the constructor/method we default it to Chunk.empty

* Add `def annotate(annotation: Any): Schema[A]` to Schema

* Replace usage of Chunk.appended with :+ to fix 2.12.12 compilation
  • Loading branch information
alexvanolst authored Nov 8, 2021
1 parent 5e89ba1 commit 151b319
Show file tree
Hide file tree
Showing 15 changed files with 471 additions and 311 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,21 @@ object JsonCodec extends Codec {
}

private[codec] def schemaEncoder[A](schema: Schema[A]): JsonEncoder[A] = schema match {
case Schema.Primitive(standardType) => primitiveCodec(standardType)
case Schema.Sequence(schema, _, g) => JsonEncoder.chunk(schemaEncoder(schema)).contramap(g)
case Schema.Transform(c, _, g) => transformEncoder(c, g)
case Schema.Tuple(l, r) => JsonEncoder.tuple2(schemaEncoder(l), schemaEncoder(r))
case Schema.Optional(schema) => JsonEncoder.option(schemaEncoder(schema))
case Schema.Fail(_) => unitEncoder.contramap(_ => ())
case Schema.GenericRecord(structure) => recordEncoder(structure.toChunk)
case EitherSchema(left, right) => JsonEncoder.either(schemaEncoder(left), schemaEncoder(right))
case l @ Schema.Lazy(_) => schemaEncoder(l.schema)
case Schema.Meta(_) => astEncoder
case ProductEncoder(encoder) => encoder
case Schema.Enum1(c, _) => enumEncoder(c)
case Schema.Enum2(c1, c2, _) => enumEncoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumEncoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumEncoder(cs.toSeq: _*)
case Schema.Primitive(standardType, _) => primitiveCodec(standardType)
case Schema.Sequence(schema, _, g, _) => JsonEncoder.chunk(schemaEncoder(schema)).contramap(g)
case Schema.Transform(c, _, g, _) => transformEncoder(c, g)
case Schema.Tuple(l, r, _) => JsonEncoder.tuple2(schemaEncoder(l), schemaEncoder(r))
case Schema.Optional(schema, _) => JsonEncoder.option(schemaEncoder(schema))
case Schema.Fail(_, _) => unitEncoder.contramap(_ => ())
case Schema.GenericRecord(structure, _) => recordEncoder(structure.toChunk)
case EitherSchema(left, right, _) => JsonEncoder.either(schemaEncoder(left), schemaEncoder(right))
case l @ Schema.Lazy(_) => schemaEncoder(l.schema)
case Schema.Meta(_, _) => astEncoder
case ProductEncoder(encoder) => encoder
case Schema.Enum1(c, _) => enumEncoder(c)
case Schema.Enum2(c1, c2, _) => enumEncoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumEncoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumEncoder(cs.toSeq: _*)
}

private val astEncoder: JsonEncoder[Schema[_]] =
Expand Down Expand Up @@ -188,21 +188,21 @@ object JsonCodec extends Codec {
schemaDecoder(schema).decodeJson(json)

private[codec] def schemaDecoder[A](schema: Schema[A]): JsonDecoder[A] = schema match {
case Schema.Primitive(standardType) => primitiveCodec(standardType)
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, f, _) => JsonDecoder.chunk(schemaDecoder(codec)).map(f)
case Schema.Fail(message) => failDecoder(message)
case Schema.GenericRecord(structure) => recordDecoder(structure.toChunk)
case Schema.EitherSchema(left, right) => JsonDecoder.either(schemaDecoder(left), schemaDecoder(right))
case l @ Schema.Lazy(_) => schemaDecoder(l.schema)
case Schema.Meta(_) => astDecoder
case ProductDecoder(decoder) => decoder
case Schema.Enum1(c, _) => enumDecoder(c)
case Schema.Enum2(c1, c2, _) => enumDecoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumDecoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumDecoder(cs.toSeq: _*)
case Schema.Primitive(standardType, _) => primitiveCodec(standardType)
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, f, _, _) => JsonDecoder.chunk(schemaDecoder(codec)).map(f)
case Schema.Fail(message, _) => failDecoder(message)
case Schema.GenericRecord(structure, _) => recordDecoder(structure.toChunk)
case Schema.EitherSchema(left, right, _) => JsonDecoder.either(schemaDecoder(left), schemaDecoder(right))
case l @ Schema.Lazy(_) => schemaDecoder(l.schema)
case Schema.Meta(_, _) => astDecoder
case ProductDecoder(decoder) => decoder
case Schema.Enum1(c, _) => enumDecoder(c)
case Schema.Enum2(c1, c2, _) => enumDecoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumDecoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumDecoder(cs.toSeq: _*)
}

private def astDecoder: JsonDecoder[Schema[_]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ object ProtobufCodec extends Codec {
*/
@scala.annotation.tailrec
def canBePacked(schema: Schema[_]): Boolean = schema match {
case Schema.Sequence(element, _, _) => canBePacked(element)
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 lzy @ Schema.Lazy(_) => canBePacked(lzy.schema)
case _ => false
case Schema.Sequence(element, _, _, _) => canBePacked(element)
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 lzy @ Schema.Lazy(_) => canBePacked(lzy.schema)
case _ => false
}

private def canBePacked(standardType: StandardType[_]): Boolean = standardType match {
Expand Down Expand Up @@ -134,21 +134,21 @@ object ProtobufCodec extends Codec {

def encode[A](fieldNumber: Option[Int], schema: Schema[A], value: A): Chunk[Byte] =
(schema, value) match {
case (Schema.GenericRecord(structure), v: Map[String, _]) => encodeRecord(fieldNumber, structure.toChunk, v)
case (Schema.Sequence(element, _, g), v) => encodeSequence(fieldNumber, element, g(v))
case (Schema.Transform(codec, _, g), _) => g(value).map(encode(fieldNumber, codec, _)).getOrElse(Chunk.empty)
case (Schema.Primitive(standardType), v) => encodePrimitive(fieldNumber, standardType, v)
case (Schema.Tuple(left, right), v @ (_, _)) => encodeTuple(fieldNumber, left, right, v)
case (Schema.Optional(codec), v: Option[_]) => encodeOptional(fieldNumber, codec, v)
case (Schema.EitherSchema(left, right), v: Either[_, _]) => encodeEither(fieldNumber, left, right, v)
case (lzy @ Schema.Lazy(_), v) => encode(fieldNumber, lzy.schema, v)
case (Schema.Meta(ast), _) => encode(fieldNumber, Schema[SchemaAst], ast)
case ProductEncoder(encode) => encode(fieldNumber)
case (Schema.Enum1(c, _), v) => encodeEnum(fieldNumber, v, c)
case (Schema.Enum2(c1, c2, _), v) => encodeEnum(fieldNumber, v, c1, c2)
case (Schema.Enum3(c1, c2, c3, _), v) => encodeEnum(fieldNumber, v, c1, c2, c3)
case (Schema.EnumN(cs, _), v) => encodeEnum(fieldNumber, v, cs.toSeq: _*)
case (_, _) => Chunk.empty
case (Schema.GenericRecord(structure, _), v: Map[String, _]) => encodeRecord(fieldNumber, structure.toChunk, v)
case (Schema.Sequence(element, _, g, _), v) => encodeSequence(fieldNumber, element, g(v))
case (Schema.Transform(codec, _, g, _), _) => g(value).map(encode(fieldNumber, codec, _)).getOrElse(Chunk.empty)
case (Schema.Primitive(standardType, _), v) => encodePrimitive(fieldNumber, standardType, v)
case (Schema.Tuple(left, right, _), v @ (_, _)) => encodeTuple(fieldNumber, left, right, v)
case (Schema.Optional(codec, _), v: Option[_]) => encodeOptional(fieldNumber, codec, v)
case (Schema.EitherSchema(left, right, _), v: Either[_, _]) => encodeEither(fieldNumber, left, right, v)
case (lzy @ Schema.Lazy(_), v) => encode(fieldNumber, lzy.schema, v)
case (Schema.Meta(ast, _), _) => encode(fieldNumber, Schema[SchemaAst], ast)
case ProductEncoder(encode) => encode(fieldNumber)
case (Schema.Enum1(c, _), v) => encodeEnum(fieldNumber, v, c)
case (Schema.Enum2(c1, c2, _), v) => encodeEnum(fieldNumber, v, c1, c2)
case (Schema.Enum3(c1, c2, c3, _), v) => encodeEnum(fieldNumber, v, c1, c2, c3)
case (Schema.EnumN(cs, _), v) => encodeEnum(fieldNumber, v, cs.toSeq: _*)
case (_, _) => Chunk.empty
}

private def encodeEnum[Z](fieldNumber: Option[Int], value: Z, cases: Schema.Case[_, Z]*): Chunk[Byte] = {
Expand Down Expand Up @@ -424,11 +424,11 @@ object ProtobufCodec extends Codec {

private[codec] def decoder[A](schema: Schema[A]): Decoder[A] =
schema match {
case Schema.GenericRecord(structure) => recordDecoder(structure.toChunk)
case Schema.Sequence(elementSchema @ Schema.Sequence(_, _, _), fromChunk, _) =>
case Schema.GenericRecord(structure, _) => recordDecoder(structure.toChunk)
case Schema.Sequence(elementSchema @ Schema.Sequence(_, _, _, _), fromChunk, _, _) =>
if (canBePacked(elementSchema)) packedSequenceDecoder(elementSchema).map(fromChunk)
else nonPackedSequenceDecoder(elementSchema).map(fromChunk)
case Schema.Sequence(elementSchema, fromChunk, _) =>
case Schema.Sequence(elementSchema, fromChunk, _, _) =>
Decoder[A](
{ bytes =>
{
Expand All @@ -443,19 +443,19 @@ object ProtobufCodec extends Codec {
},
true
)
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 lzy @ Schema.Lazy(_) => decoder(lzy.schema)
case Schema.Meta(_) => astDecoder
case ProductDecoder(decoder) => decoder
case Schema.Enum1(c, _) => enumDecoder(c)
case Schema.Enum2(c1, c2, _) => enumDecoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumDecoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumDecoder(cs.toSeq: _*)
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 lzy @ Schema.Lazy(_) => decoder(lzy.schema)
case Schema.Meta(_, _) => astDecoder
case ProductDecoder(decoder) => decoder
case Schema.Enum1(c, _) => enumDecoder(c)
case Schema.Enum2(c1, c2, _) => enumDecoder(c1, c2)
case Schema.Enum3(c1, c2, c3, _) => enumDecoder(c1, c2, c3)
case Schema.EnumN(cs, _) => enumDecoder(cs.toSeq: _*)
}

private val astDecoder: Decoder[Schema[_]] =
Expand Down Expand Up @@ -563,7 +563,7 @@ object ProtobufCodec extends Codec {

private def transformDecoder[A, B](schema: Schema[B], f: B => Either[String, A]): Decoder[A] =
schema match {
case Schema.Primitive(typ) if typ == StandardType.UnitType =>
case Schema.Primitive(typ, _) if typ == StandardType.UnitType =>
Decoder { (chunk: Chunk[Byte]) =>
f(().asInstanceOf[B]) match {
case Left(err) => Left(err)
Expand Down
Loading

0 comments on commit 151b319

Please sign in to comment.