Skip to content

Commit ce3a315

Browse files
authored
Non empty collection schemas (#717) (#723)
1 parent b178062 commit ce3a315

File tree

17 files changed

+433
-106
lines changed

17 files changed

+433
-106
lines changed

build.sbt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ inThisBuild(
3939
ThisBuild / publishTo := sonatypePublishToBundle.value
4040
scalacOptions ++= Seq("-scalajs")
4141

42-
addCommandAlias("prepare", "fix; fmt")
43-
addCommandAlias("fmt", "all scalafmtSbt scalafmtAll")
42+
addCommandAlias("fmt", "all scalafmtSbt scalafmtAll;fix")
4443
addCommandAlias("fmtCheck", "all scalafmtSbtCheck scalafmtCheckAll")
4544
addCommandAlias("fix", "scalafixAll")
4645
addCommandAlias("fixCheck", "scalafixAll --check")

tests/shared/src/test/scala/zio/schema/DynamicValueGen.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ object DynamicValueGen {
7474
case Schema.Enum22(_, case1, case2, case3, case4, case5, case6, case7, case8, case9, case10, case11, case12, case13, case14, case15, case16, case17, case18, case19, case20, case21, case22, _) => anyDynamicValueOfEnum(Chunk(case1, case2, case3, case4, case5, case6, case7, case8, case9, case10, case11, case12, case13, case14, case15, case16, case17, case18, case19, case20, case21, case22))
7575
case Schema.EnumN(_, cases, _) => anyDynamicValueOfEnum(Chunk.fromIterable(cases.toSeq))
7676
case Schema.Sequence(schema, _, _, _, _) => Gen.chunkOfBounded(0, 2)(anyDynamicValueOfSchema(schema)).map(DynamicValue.Sequence(_))
77+
case Schema.NonEmptySequence(schema, _, _, _, _) => Gen.chunkOfBounded(1, 2)(anyDynamicValueOfSchema(schema)).map(DynamicValue.Sequence(_))
7778
case Schema.Map(ks, vs, _) => Gen.chunkOfBounded(0, 2)(anyDynamicValueOfSchema(ks).zip(anyDynamicValueOfSchema(vs))).map(DynamicValue.Dictionary(_))
79+
case Schema.NonEmptyMap(ks, vs, _) => Gen.chunkOfBounded(1, 2)(anyDynamicValueOfSchema(ks).zip(anyDynamicValueOfSchema(vs))).map(DynamicValue.Dictionary(_))
7880
case Schema.Set(schema, _) => Gen.setOfBounded(0, 2)(anyDynamicValueOfSchema(schema)).map(DynamicValue.SetValue(_))
7981
case Schema.Optional(schema, _) => Gen.oneOf(anyDynamicSomeValueOfSchema(schema), Gen.const(DynamicValue.NoneValue))
8082
case Schema.Tuple2(left, right, _) => anyDynamicTupleValue(left, right)

zio-schema-avro/src/main/scala/zio/schema/codec/AvroCodec.scala

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.apache.avro.io.{ DecoderFactory, EncoderFactory }
1919
import org.apache.avro.util.Utf8
2020
import org.apache.avro.{ Conversions, LogicalTypes, Schema => SchemaAvro }
2121

22+
import zio.prelude.NonEmptyMap
2223
import zio.schema.{ Fallback, FieldSet, Schema, StandardType, TypeId }
2324
import zio.stream.ZPipeline
2425
import zio.{ Chunk, Unsafe, ZIO }
@@ -201,9 +202,20 @@ object AvroCodec {
201202
case record: Schema.Record[_] => decodeRecord(raw, record).map(_.asInstanceOf[A])
202203
case Schema.Sequence(element, f, _, _, _) =>
203204
decodeSequence(raw, element.asInstanceOf[Schema[Any]]).map(f.asInstanceOf[Chunk[Any] => A])
205+
case nes @ Schema.NonEmptySequence(element, _, _, _, _) =>
206+
decodeSequence(raw, element.asInstanceOf[Schema[Any]]).map(nes.fromChunk.asInstanceOf[Chunk[Any] => A])
204207
case Schema.Set(element, _) => decodeSequence(raw, element.asInstanceOf[Schema[Any]]).map(_.toSet.asInstanceOf[A])
205208
case mapSchema: Schema.Map[_, _] =>
206209
decodeMap(raw, mapSchema.asInstanceOf[Schema.Map[Any, Any]]).map(_.asInstanceOf[A])
210+
case mapSchema: Schema.NonEmptyMap[_, _] =>
211+
decodeMap(
212+
raw,
213+
Schema.Map(
214+
mapSchema.keySchema.asInstanceOf[Schema[Any]],
215+
mapSchema.valueSchema.asInstanceOf[Schema[Any]],
216+
mapSchema.annotations
217+
)
218+
).map(mapSchema.asInstanceOf[Schema.NonEmptyMap[Any, Any]].fromMap(_).asInstanceOf[A])
207219
case Schema.Transform(schema, f, _, _, _) =>
208220
decodeValue(raw, schema).flatMap(
209221
a => f(a).left.map(msg => DecodeError.MalformedFieldWithPath(Chunk.single("Error"), msg))
@@ -662,12 +674,22 @@ object AvroCodec {
662674
c21,
663675
c22
664676
)
665-
case Schema.GenericRecord(typeId, structure, _) => encodeGenericRecord(a, typeId, structure)
666-
case Schema.Primitive(standardType, _) => encodePrimitive(a, standardType)
667-
case Schema.Sequence(element, _, g, _, _) => encodeSequence(element, g(a))
668-
case Schema.Set(element, _) => encodeSet(element, a)
677+
case Schema.GenericRecord(typeId, structure, _) => encodeGenericRecord(a, typeId, structure)
678+
case Schema.Primitive(standardType, _) => encodePrimitive(a, standardType)
679+
case Schema.Sequence(element, _, g, _, _) => encodeSequence(element, g(a))
680+
case Schema.NonEmptySequence(element, _, g, _, _) => encodeSequence(element, g(a))
681+
case Schema.Set(element, _) => encodeSet(element, a)
669682
case mapSchema: Schema.Map[_, _] =>
670683
encodeMap(mapSchema.asInstanceOf[Schema.Map[Any, Any]], a.asInstanceOf[scala.collection.immutable.Map[Any, Any]])
684+
case mapSchema: Schema.NonEmptyMap[_, _] =>
685+
encodeMap(
686+
Schema.Map(
687+
mapSchema.keySchema.asInstanceOf[Schema[Any]],
688+
mapSchema.valueSchema.asInstanceOf[Schema[Any]],
689+
mapSchema.annotations
690+
),
691+
a.asInstanceOf[NonEmptyMap[Any, Any]].toMap
692+
)
671693
case Schema.Transform(schema, _, g, _, _) =>
672694
g(a).map(encodeValue(_, schema)).getOrElse(throw new Exception("Transform failed."))
673695
case Schema.Optional(schema, _) => encodeOption(schema, a)

zio-schema-avro/src/main/scala/zio/schema/codec/AvroSchemaCodec.scala

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,14 @@ object AvroSchemaCodec extends AvroSchemaCodec {
277277

278278
private def toAvroSchema(schema: Schema[_]): scala.util.Either[String, SchemaAvro] = {
279279
schema match {
280-
case e: Enum[_] => toAvroEnum(e)
281-
case record: Record[_] => toAvroRecord(record)
282-
case map: Schema.Map[_, _] => toAvroMap(map)
283-
case seq: Schema.Sequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray)
284-
case set: Schema.Set[_] => toAvroSchema(set.elementSchema).map(SchemaAvro.createArray)
285-
case Transform(codec, _, _, _, _) => toAvroSchema(codec)
280+
case e: Enum[_] => toAvroEnum(e)
281+
case record: Record[_] => toAvroRecord(record)
282+
case map: Schema.Map[_, _] => toAvroMap(map)
283+
case map: Schema.NonEmptyMap[_, _] => toAvroMap(map)
284+
case seq: Schema.Sequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray)
285+
case seq: Schema.NonEmptySequence[_, _, _] => toAvroSchema(seq.elementSchema).map(SchemaAvro.createArray)
286+
case set: Schema.Set[_] => toAvroSchema(set.elementSchema).map(SchemaAvro.createArray)
287+
case Transform(codec, _, _, _, _) => toAvroSchema(codec)
286288
case Primitive(standardType, _) =>
287289
standardType match {
288290
case StandardType.UnitType => Right(SchemaAvro.create(SchemaAvro.Type.NULL))
@@ -624,6 +626,18 @@ object AvroSchemaCodec extends AvroSchemaCodec {
624626
toAvroSchema(tupleSchema).map(SchemaAvro.createArray)
625627
}
626628

629+
private[codec] def toAvroMap(map: NonEmptyMap[_, _]): scala.util.Either[String, SchemaAvro] =
630+
map.keySchema match {
631+
case p: Schema.Primitive[_] if p.standardType == StandardType.StringType =>
632+
toAvroSchema(map.valueSchema).map(SchemaAvro.createMap)
633+
case _ =>
634+
val tupleSchema = Schema
635+
.Tuple2(map.keySchema, map.valueSchema)
636+
.annotate(AvroAnnotations.name("Tuple"))
637+
.annotate(AvroAnnotations.namespace("scala"))
638+
toAvroSchema(tupleSchema).map(SchemaAvro.createArray)
639+
}
640+
627641
private[codec] def toAvroDecimal(schema: Schema[_]): scala.util.Either[String, SchemaAvro] = {
628642
val scale = schema.annotations.collectFirst { case AvroAnnotations.scale(s) => s }
629643
.getOrElse(AvroAnnotations.scale().scale)
@@ -820,7 +834,9 @@ object AvroSchemaCodec extends AvroSchemaCodec {
820834
case c: Dynamic => Right(c)
821835
case c: GenericRecord => Right(c)
822836
case c: Map[_, _] => Right(c)
837+
case c: NonEmptyMap[_, _] => Right(c)
823838
case c: Sequence[_, _, _] => Right(c)
839+
case c: NonEmptySequence[_, _, _] => Right(c)
824840
case c: Set[_] => Right(c)
825841
case c: Fail[_] => Right(c)
826842
case c: Lazy[_] => Right(c)

zio-schema-avro/src/test/scala/zio/schema/codec/AvroSchemaCodecSpec.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,10 +1918,7 @@ object AssertionHelper {
19181918
def recordFields(assertion: Assertion[Iterable[Schema.Field[_, _]]]): Assertion[Schema.Record[_]] =
19191919
Assertion.assertionRec[Schema.Record[_], Chunk[Field[_, _]]]("hasRecordField")(
19201920
assertion
1921-
) {
1922-
case r: Schema.Record[_] => Some(r.fields)
1923-
case _ => None
1924-
}
1921+
)((r: Schema.Record[_]) => Some(r.fields))
19251922

19261923
def hasSequenceElementSchema[A](assertion: Assertion[Schema[A]]): Assertion[Schema.Sequence[_, A, _]] =
19271924
Assertion.hasField("schemaA", _.elementSchema, assertion)

zio-schema-bson/src/main/scala/zio/schema/codec/BsonSchemaCodec.scala

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -457,22 +457,24 @@ object BsonSchemaCodec {
457457
//scalafmt: { maxColumn = 400, optIn.configStyleArguments = false }
458458
private[codec] def schemaEncoder[A](schema: Schema[A]): BsonEncoder[A] =
459459
schema match {
460-
case Schema.Primitive(standardType, _) => primitiveCodec(standardType).encoder
461-
case Schema.Sequence(schema, _, g, _, _) => chunkEncoder(schemaEncoder(schema)).contramap(g)
462-
case Schema.Map(ks, vs, _) => mapEncoder(ks, vs)
463-
case Schema.Set(s, _) => chunkEncoder(schemaEncoder(s)).contramap(m => Chunk.fromIterable(m))
464-
case Schema.Transform(c, _, g, _, _) => transformEncoder(c, g)
465-
case Schema.Tuple2(l, r, _) => tuple2Encoder(schemaEncoder(l), schemaEncoder(r))
466-
case Schema.Optional(schema, _) => BsonEncoder.option(schemaEncoder(schema))
467-
case Schema.Fail(_, _) => unitEncoder.contramap(_ => ())
468-
case Schema.GenericRecord(_, structure, _) => genericRecordEncoder(structure.toChunk)
469-
case Schema.Either(left, right, _) => eitherEncoder(schemaEncoder(left), schemaEncoder(right))
470-
case Schema.Fallback(left, right, _, _) => fallbackEncoder(schemaEncoder(left), schemaEncoder(right))
471-
case l @ Schema.Lazy(_) => schemaEncoder(l.schema)
472-
case r: Schema.Record[A] => caseClassEncoder(r)
473-
case e: Schema.Enum[A] => enumEncoder(e, e.cases)
474-
case d @ Schema.Dynamic(_) => dynamicEncoder(d)
475-
case null => throw new Exception(s"A captured schema is null, most likely due to wrong field initialization order")
460+
case Schema.Primitive(standardType, _) => primitiveCodec(standardType).encoder
461+
case Schema.Sequence(schema, _, g, _, _) => chunkEncoder(schemaEncoder(schema)).contramap(g)
462+
case Schema.NonEmptySequence(schema, _, g, _, _) => chunkEncoder(schemaEncoder(schema)).contramap(g)
463+
case Schema.Map(ks, vs, _) => mapEncoder(ks, vs)
464+
case Schema.NonEmptyMap(ks, vs, _) => mapEncoder(ks, vs).contramap(_.toMap)
465+
case Schema.Set(s, _) => chunkEncoder(schemaEncoder(s)).contramap(m => Chunk.fromIterable(m))
466+
case Schema.Transform(c, _, g, _, _) => transformEncoder(c, g)
467+
case Schema.Tuple2(l, r, _) => tuple2Encoder(schemaEncoder(l), schemaEncoder(r))
468+
case Schema.Optional(schema, _) => BsonEncoder.option(schemaEncoder(schema))
469+
case Schema.Fail(_, _) => unitEncoder.contramap(_ => ())
470+
case Schema.GenericRecord(_, structure, _) => genericRecordEncoder(structure.toChunk)
471+
case Schema.Either(left, right, _) => eitherEncoder(schemaEncoder(left), schemaEncoder(right))
472+
case Schema.Fallback(left, right, _, _) => fallbackEncoder(schemaEncoder(left), schemaEncoder(right))
473+
case l @ Schema.Lazy(_) => schemaEncoder(l.schema)
474+
case r: Schema.Record[A] => caseClassEncoder(r)
475+
case e: Schema.Enum[A] => enumEncoder(e, e.cases)
476+
case d @ Schema.Dynamic(_) => dynamicEncoder(d)
477+
case null => throw new Exception(s"A captured schema is null, most likely due to wrong field initialization order")
476478
}
477479
//scalafmt: { maxColumn = 120, optIn.configStyleArguments = true }
478480

@@ -773,22 +775,24 @@ object BsonSchemaCodec {
773775

774776
//scalafmt: { maxColumn = 400, optIn.configStyleArguments = false }
775777
private[codec] def schemaDecoder[A](schema: Schema[A]): BsonDecoder[A] = schema match {
776-
case Schema.Primitive(standardType, _) => primitiveCodec(standardType).decoder
777-
case Schema.Optional(codec, _) => BsonDecoder.option(schemaDecoder(codec))
778-
case Schema.Tuple2(left, right, _) => tuple2Decoder(schemaDecoder(left), schemaDecoder(right))
779-
case Schema.Transform(codec, f, _, _, _) => schemaDecoder(codec).mapOrFail(f)
780-
case Schema.Sequence(codec, f, _, _, _) => chunkDecoder(schemaDecoder(codec)).map(f)
781-
case Schema.Map(ks, vs, _) => mapDecoder(ks, vs)
782-
case Schema.Set(s, _) => chunkDecoder(schemaDecoder(s)).map(entries => entries.toSet)
783-
case Schema.Fail(message, _) => failDecoder(message)
784-
case Schema.GenericRecord(_, structure, _) => recordDecoder(structure.toChunk)
785-
case Schema.Either(left, right, _) => eitherDecoder(schemaDecoder(left), schemaDecoder(right))
786-
case Schema.Fallback(left, right, _, _) => fallbackDecoder(schemaDecoder(left), schemaDecoder(right))
787-
case l @ Schema.Lazy(_) => schemaDecoder(l.schema)
788-
case s: Schema.Record[A] => caseClassDecoder(s)
789-
case e: Schema.Enum[A] => enumDecoder(e)
790-
case d @ Schema.Dynamic(_) => dynamicDecoder(d)
791-
case null => throw new Exception(s"Missing a handler for decoding of schema $schema.")
778+
case Schema.Primitive(standardType, _) => primitiveCodec(standardType).decoder
779+
case Schema.Optional(codec, _) => BsonDecoder.option(schemaDecoder(codec))
780+
case Schema.Tuple2(left, right, _) => tuple2Decoder(schemaDecoder(left), schemaDecoder(right))
781+
case Schema.Transform(codec, f, _, _, _) => schemaDecoder(codec).mapOrFail(f)
782+
case Schema.Sequence(codec, f, _, _, _) => chunkDecoder(schemaDecoder(codec)).map(f)
783+
case s @ Schema.NonEmptySequence(codec, _, _, _, _) => chunkDecoder(schemaDecoder(codec)).map(s.fromChunk)
784+
case Schema.Map(ks, vs, _) => mapDecoder(ks, vs)
785+
case s @ Schema.NonEmptyMap(ks, vs, _) => mapDecoder(ks, vs).map(s.fromMap)
786+
case Schema.Set(s, _) => chunkDecoder(schemaDecoder(s)).map(entries => entries.toSet)
787+
case Schema.Fail(message, _) => failDecoder(message)
788+
case Schema.GenericRecord(_, structure, _) => recordDecoder(structure.toChunk)
789+
case Schema.Either(left, right, _) => eitherDecoder(schemaDecoder(left), schemaDecoder(right))
790+
case Schema.Fallback(left, right, _, _) => fallbackDecoder(schemaDecoder(left), schemaDecoder(right))
791+
case l @ Schema.Lazy(_) => schemaDecoder(l.schema)
792+
case s: Schema.Record[A] => caseClassDecoder(s)
793+
case e: Schema.Enum[A] => enumDecoder(e)
794+
case d @ Schema.Dynamic(_) => dynamicDecoder(d)
795+
case _ => throw new Exception(s"Missing a handler for decoding of schema $schema.")
792796
}
793797
//scalafmt: { maxColumn = 120, optIn.configStyleArguments = true }
794798

zio-schema-bson/src/test/scala/zio/schema/codec/BsonSchemaCodecSpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ object BsonSchemaCodecSpec extends ZIOSpecDefault {
4040
implicit lazy val schema: Schema[Tree] = DeriveSchema.gen
4141
implicit lazy val codec: BsonCodec[Tree] = BsonSchemaCodec.bsonCodec(schema)
4242

43-
private val genLeaf = Gen.int.map(Leaf)
43+
private val genLeaf = Gen.int.map(Leaf.apply)
4444

4545
lazy val gen: Gen[Any, Tree] = Gen.sized { i =>
46-
if (i >= 2) Gen.oneOf(genLeaf, Gen.suspend(gen.zipWith(gen)(Branch)).resize(i / 2))
46+
if (i >= 2) Gen.oneOf(genLeaf, Gen.suspend(gen.zipWith(gen)(Branch.apply)).resize(i / 2))
4747
else genLeaf
4848
}
4949
}

0 commit comments

Comments
 (0)