From fe1d61394d1f8514ad882e0aecf42687bfde8375 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 4 Jul 2022 11:51:50 -0400 Subject: [PATCH 01/13] Schema refactor in core --- .../smithy4s/http/internals/MetaDecode.scala | 11 ++-- .../SchemaVisitorMetadataReader.scala | 24 +++------ .../SchemaVisitorMetadataWriter.scala | 20 ++------ .../internals/SchemaVisitorPathEncoder.scala | 11 ++-- .../internals/SchematicDocumentDecoder.scala | 27 ++++------ .../internals/SchematicDocumentEncoder.scala | 19 +++---- .../src/smithy4s/schema/CollectionTag.scala | 51 +++++++++++++++++++ .../schema/PassthroughSchematic.scala | 5 +- modules/core/src/smithy4s/schema/Schema.scala | 16 +++--- .../src/smithy4s/schema/SchemaVisitor.scala | 6 +-- .../core/src/smithy4s/schema/Schematic.scala | 9 ++-- .../src/smithy4s/schema/SchematicRepr.scala | 5 +- .../src/smithy4s/schema/StubSchematic.scala | 7 +-- .../src/smithy4s/ShapeIdHintsSmokeSpec.scala | 5 +- .../internals/FromMetadataSchematic.scala | 11 ++-- 15 files changed, 118 insertions(+), 109 deletions(-) create mode 100644 modules/core/src/smithy4s/schema/CollectionTag.scala diff --git a/modules/core/src/smithy4s/http/internals/MetaDecode.scala b/modules/core/src/smithy4s/http/internals/MetaDecode.scala index c0d93f3e7..ab1955113 100644 --- a/modules/core/src/smithy4s/http/internals/MetaDecode.scala +++ b/modules/core/src/smithy4s/http/internals/MetaDecode.scala @@ -28,8 +28,9 @@ import HttpBinding._ private[http] sealed abstract class MetaDecode[+A] { def map[B](to: A => B): MetaDecode[B] = this match { - case StringValueMetaDecode(f) => StringValueMetaDecode(f andThen to) - case StringListMetaDecode(f) => StringListMetaDecode(f andThen to) + case StringValueMetaDecode(f) => StringValueMetaDecode(f andThen to) + case StringCollectionMetaDecode(f) => + StringCollectionMetaDecode(f andThen to) case StringMapMetaDecode(f) => StringMapMetaDecode(f andThen to) case StringListMapMetaDecode(f) => StringListMapMetaDecode(f andThen to) case EmptyMetaDecode => EmptyMetaDecode @@ -76,7 +77,7 @@ private[http] sealed abstract class MetaDecode[+A] { putField(fieldName, f(values.head)) } else throw MetadataError.ArityError(fieldName, binding) } - case (HeaderBinding(h), StringListMetaDecode(f)) => + case (HeaderBinding(h), StringCollectionMetaDecode(f)) => lookupAndProcess(_.headers, h) { (values, fieldName, putField) => putField(fieldName, f(values.iterator)) } @@ -86,7 +87,7 @@ private[http] sealed abstract class MetaDecode[+A] { putField(fieldName, f(values.head)) } else throw MetadataError.ArityError(fieldName, binding) } - case (QueryBinding(q), StringListMetaDecode(f)) => + case (QueryBinding(q), StringCollectionMetaDecode(f)) => lookupAndProcess(_.query, q) { (values, fieldName, putField) => putField(fieldName, f(values.iterator)) } @@ -154,7 +155,7 @@ private[http] object MetaDecode { // format: off final case class StringValueMetaDecode[A](f: String => A) extends MetaDecode[A] - final case class StringListMetaDecode[A](f: Iterator[String] => A) extends MetaDecode[A] + final case class StringCollectionMetaDecode[A](f: Iterator[String] => A) extends MetaDecode[A] final case class StringMapMetaDecode[A](f: Iterator[(String, String)] => A) extends MetaDecode[A] final case class StringListMapMetaDecode[A](f: Iterator[(String, Iterator[String])] => A) extends MetaDecode[A] case object EmptyMetaDecode extends MetaDecode[Nothing] diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala index 7e81c0c2a..ab9d8a38a 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala @@ -22,7 +22,7 @@ import smithy4s.http.internals.MetaDecode.{ EmptyMetaDecode, PutField, StringListMapMetaDecode, - StringListMetaDecode, + StringCollectionMetaDecode, StringMapMetaDecode, StringValueMetaDecode, StructureMetaDecode @@ -98,29 +98,21 @@ private[http] class SchemaVisitorMetadataReader() } } - override def list[A]( + override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, + tag: CollectionTag[C, A], member: Schema[A] - ): MetaDecode[List[A]] = { + ): MetaDecode[C[A]] = { self(member) match { case MetaDecode.StringValueMetaDecode(f) => - MetaDecode.StringListMetaDecode[List[A]](_.map(f).toList) + MetaDecode.StringCollectionMetaDecode[C[A]] { it => + tag.fromIterator(it.map(f)) + } case _ => EmptyMetaDecode } } - override def set[A]( - shapeId: ShapeId, - hints: Hints, - member: Schema[A] - ): MetaDecode[Set[A]] = - self(member) match { - case MetaDecode.StringValueMetaDecode(f) => - MetaDecode.StringListMetaDecode[Set[A]](_.map(f).toSet) - case _ => EmptyMetaDecode - } - override def map[K, V]( shapeId: ShapeId, hints: Hints, @@ -132,7 +124,7 @@ private[http] class SchemaVisitorMetadataReader() StringMapMetaDecode[Map[K, V]](map => map.map { case (k, v) => (readK(k), readV(v)) }.toMap ) - case (StringValueMetaDecode(readK), StringListMetaDecode(readV)) => + case (StringValueMetaDecode(readK), StringCollectionMetaDecode(readV)) => StringListMapMetaDecode[Map[K, V]](map => map.map { case (k, v) => (readK(k), readV(v)) }.toMap ) diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala index 6a74a2d63..fdd4d7a45 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala @@ -22,6 +22,7 @@ import smithy4s.http.HttpBinding import smithy4s.http.internals.MetaEncode._ import smithy4s.schema.Alt.SchemaAndValue import smithy4s.schema.{ + CollectionTag, EnumValue, Field, Primitive, @@ -84,26 +85,15 @@ object SchemaVisitorMetadataWriter extends SchemaVisitor[MetaEncode] { self => } } - override def list[A]( + override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, + tag: CollectionTag[C, A], member: Schema[A] - ): MetaEncode[List[A]] = { + ): MetaEncode[C[A]] = { self(member) match { case StringValueMetaEncode(f) => - StringListMetaEncode[List[A]](listA => listA.map(f)) - case _ => MetaEncode.empty - } - } - - override def set[A]( - shapeId: ShapeId, - hints: Hints, - member: Schema[A] - ): MetaEncode[Set[A]] = { - self(member) match { - case StringValueMetaEncode(f) => - StringListMetaEncode[Set[A]](set => set.map(f).toList) + StringListMetaEncode[C[A]](c => tag.iterator(c).map(f).toList) case _ => MetaEncode.empty } } diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala index 9c1ef40fa..724d67aa2 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala @@ -57,17 +57,12 @@ object SchemaVisitorPathEncoder extends SchemaVisitor[MaybePathEncode] { self => } } - override def list[A]( + override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, + tag: CollectionTag[C, A], member: Schema[A] - ): MaybePathEncode[List[A]] = default - - override def set[A]( - shapeId: ShapeId, - hints: Hints, - member: Schema[A] - ): MaybePathEncode[Set[A]] = default + ): MaybePathEncode[C[A]] = default override def map[K, V]( shapeId: ShapeId, diff --git a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala index 0bfe9c393..82aa2ff7b 100644 --- a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala +++ b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala @@ -229,25 +229,18 @@ object SchematicDocumentDecoder extends Schematic[DocumentDecoderMake] { () }) - def list[S](fs: DocumentDecoderMake[S]): DocumentDecoderMake[List[S]] = + def collection[C[_], S]( + tag: CollectionTag[C, S], + fs: DocumentDecoderMake[S] + ): DocumentDecoderMake[C[S]] = fs.transform { fa => - DocumentDecoder.instance("List", "Array", false) { + DocumentDecoder.instance("Collection", "Array", false) { case (pp, DArray(value)) => - value.zipWithIndex.map { case (document, index) => - val localPath = PayloadPath.Segment(index) :: pp - fa(localPath, document) - }.toList - } - } - - def set[S](fs: DocumentDecoderMake[S]): DocumentDecoderMake[Set[S]] = - fs.transform { fa => - DocumentDecoder.instance("Set", "Array", false) { - case (pp, DArray(value)) => - value.zipWithIndex.map { case (document, index) => - val localPath = PayloadPath.Segment(index) :: pp - fa(localPath, document) - }.toSet + tag.fromIterator(value.iterator.zipWithIndex.map { + case (document, index) => + val localPath = PayloadPath.Segment(index) :: pp + fa(localPath, document) + }) } } diff --git a/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala b/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala index e724b6beb..79bc7b426 100644 --- a/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala +++ b/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala @@ -113,19 +113,14 @@ object SchematicDocumentEncoder extends Schematic[DocumentEncoderMake] { def unit: DocumentEncoderMake[Unit] = fromNotKey(_ => DObject(Map.empty)) - def list[S](fs: DocumentEncoderMake[S]): DocumentEncoderMake[List[S]] = + def collection[C[_], S]( + tag: CollectionTag[C, S], + fs: DocumentEncoderMake[S] + ): DocumentEncoderMake[C[S]] = fs.transform(encoderS => - fromNotKey[List[S]](l => DArray(l.map(encoderS.apply).toIndexedSeq)).get - ) - - def set[S](fs: DocumentEncoderMake[S]): DocumentEncoderMake[Set[S]] = - fs.transform(encoderS => - fromNotKey[Set[S]](s => DArray(s.map(encoderS.apply).toIndexedSeq)).get - ) - - def vector[S](fs: DocumentEncoderMake[S]): DocumentEncoderMake[Vector[S]] = - fs.transform(encoderS => - fromNotKey[Vector[S]](v => DArray(v.map(encoderS.apply).toIndexedSeq)).get + fromNotKey[C[S]](c => + DArray(tag.iterator(c).map(encoderS.apply).toIndexedSeq) + ).get ) def map[K, V]( diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala new file mode 100644 index 000000000..c994981f7 --- /dev/null +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smithy4s +package schema + +sealed trait CollectionTag[C[_], A] { + def iterator(c: C[A]): Iterator[A] + def build(put: (A => Unit) => Unit): C[A] + + def fromIterator(it: Iterator[A]): C[A] = build(put => it.foreach(put(_))) + def empty: C[A] = build(_ => ()) +} + +object CollectionTag { + def list[A]: CollectionTag[List, A] = new CollectionTag[List, A] { + + override def iterator(c: List[A]): Iterator[A] = c.iterator + + override def build(put: (A => Unit) => Unit): List[A] = { + val builder = List.newBuilder[A] + put(builder.+=(_)) + builder.result() + } + + } + def set[A]: CollectionTag[Set, A] = new CollectionTag[Set, A] { + + override def iterator(c: Set[A]): Iterator[A] = c.iterator + + override def build(put: (A => Unit) => Unit): Set[A] = { + val builder = Set.newBuilder[A] + put(builder.+=(_)) + builder.result() + } + + } +} diff --git a/modules/core/src/smithy4s/schema/PassthroughSchematic.scala b/modules/core/src/smithy4s/schema/PassthroughSchematic.scala index bd9f55f4b..de4bd8d3d 100644 --- a/modules/core/src/smithy4s/schema/PassthroughSchematic.scala +++ b/modules/core/src/smithy4s/schema/PassthroughSchematic.scala @@ -46,9 +46,8 @@ class PassthroughSchematic[F[_]](schematic: Schematic[F]) extends Schematic[F] { def unit: F[Unit] = schematic.unit - def list[S](fs: F[S]): F[List[S]] = schematic.list(fs) - - def set[S](fs: F[S]): F[Set[S]] = schematic.set(fs) + def collection[C[_], S](tag: CollectionTag[C, S], fs: F[S]): F[C[S]] = + schematic.collection(tag, fs) def map[K, V](fk: F[K], fv: F[V]): F[Map[K, V]] = schematic.map(fk, fv) diff --git a/modules/core/src/smithy4s/schema/Schema.scala b/modules/core/src/smithy4s/schema/Schema.scala index 0845bb67e..59454717c 100644 --- a/modules/core/src/smithy4s/schema/Schema.scala +++ b/modules/core/src/smithy4s/schema/Schema.scala @@ -36,8 +36,7 @@ sealed trait Schema[A]{ final def withId(newId: ShapeId) : Schema[A] = this match { case PrimitiveSchema(_, hints, tag) => PrimitiveSchema(newId, hints, tag) - case s: ListSchema[a] => ListSchema(newId, s.hints, s.member).asInstanceOf[Schema[A]] - case s: SetSchema[a] => SetSchema(newId, s.hints, s.member).asInstanceOf[Schema[A]] + case s: CollectionSchema[c, a] => CollectionSchema(newId, s.hints, s.tag, s.member).asInstanceOf[Schema[A]] case s: MapSchema[k, v] => MapSchema(newId, s.hints, s.key, s.value).asInstanceOf[Schema[A]] case EnumerationSchema(_, hints, values, total) => EnumerationSchema(newId, hints, values, total) case StructSchema(_, hints, fields, make) => StructSchema(newId, hints, fields, make) @@ -52,8 +51,7 @@ sealed trait Schema[A]{ final def transformHintsLocally(f: Hints => Hints) : Schema[A] = this match { case PrimitiveSchema(shapeId, hints, tag) => PrimitiveSchema(shapeId, f(hints), tag) - case s: ListSchema[a] => ListSchema(s.shapeId, f(s.hints), s.member).asInstanceOf[Schema[A]] - case s: SetSchema[a] => SetSchema(s.shapeId, f(s.hints), s.member).asInstanceOf[Schema[A]] + case s: CollectionSchema[c, a] => CollectionSchema(s.shapeId, f(s.hints), s.tag, s.member).asInstanceOf[Schema[A]] case s: MapSchema[k, v] => MapSchema(s.shapeId, f(s.hints), s.key, s.value).asInstanceOf[Schema[A]] case EnumerationSchema(shapeId, hints, values, total) => EnumerationSchema(shapeId, f(hints), values, total) case StructSchema(shapeId, hints, fields, make) => StructSchema(shapeId, f(hints), fields, make) @@ -65,8 +63,7 @@ sealed trait Schema[A]{ final def transformHintsTransitively(f: Hints => Hints) : Schema[A] = this match { case PrimitiveSchema(shapeId, hints, tag) => PrimitiveSchema(shapeId, f(hints), tag) - case s: ListSchema[a] => ListSchema(s.shapeId, f(s.hints), s.member.transformHintsTransitively(f)).asInstanceOf[Schema[A]] - case s: SetSchema[a] => SetSchema(s.shapeId, f(s.hints), s.member.transformHintsTransitively(f)).asInstanceOf[Schema[A]] + case s: CollectionSchema[c, a] => CollectionSchema[c, a](s.shapeId, f(s.hints), s.tag, s.member.transformHintsTransitively(f)).asInstanceOf[Schema[A]] case s: MapSchema[k, v] => MapSchema(s.shapeId, f(s.hints), s.key.transformHintsTransitively(f), s.value.transformHintsTransitively(f)).asInstanceOf[Schema[A]] case EnumerationSchema(shapeId, hints, values, total) => EnumerationSchema(shapeId, f(hints), values.map(_.transformHints(f)), total) case StructSchema(shapeId, hints, fields, make) => StructSchema(shapeId, f(hints), fields.map(_.mapK(Schema.transformHintsTransitivelyK(f))), make) @@ -91,8 +88,7 @@ sealed trait Schema[A]{ object Schema { final case class PrimitiveSchema[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) extends Schema[P] - final case class ListSchema[A](shapeId: ShapeId, hints: Hints, member: Schema[A]) extends Schema[List[A]] - final case class SetSchema[A](shapeId: ShapeId, hints: Hints, member: Schema[A]) extends Schema[Set[A]] + final case class CollectionSchema[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C, A], member: Schema[A]) extends Schema[C[A]] final case class MapSchema[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]) extends Schema[Map[K, V]] final case class EnumerationSchema[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]) extends Schema[E] final case class StructSchema[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S) extends Schema[S] @@ -141,8 +137,8 @@ object Schema { private val placeholder: ShapeId = ShapeId("placeholder", "Placeholder") - def list[A](a: Schema[A]): Schema[List[A]] = Schema.ListSchema(placeholder, Hints.empty, a) - def set[A](a: Schema[A]): Schema[Set[A]] = Schema.SetSchema(placeholder, Hints.empty, a) + def list[A](a: Schema[A]): Schema[List[A]] = Schema.CollectionSchema[List, A](placeholder, Hints.empty, CollectionTag.list, a) + def set[A](a: Schema[A]): Schema[Set[A]] = Schema.CollectionSchema[Set, A](placeholder, Hints.empty, CollectionTag.set, a) def map[K, V](k: Schema[K], v: Schema[V]): Schema[Map[K, V]] = Schema.MapSchema(placeholder, Hints.empty, k, v) def recursive[A](s : => Schema[A]) : Schema[A] = Schema.LazySchema(Lazy(s)) diff --git a/modules/core/src/smithy4s/schema/SchemaVisitor.scala b/modules/core/src/smithy4s/schema/SchemaVisitor.scala index 88185d2b4..4c00be70d 100644 --- a/modules/core/src/smithy4s/schema/SchemaVisitor.scala +++ b/modules/core/src/smithy4s/schema/SchemaVisitor.scala @@ -22,8 +22,7 @@ import Schema._ // format: off trait SchemaVisitor[F[_]] extends (Schema ~> F) { def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) : F[P] - def list[A](shapeId: ShapeId, hints: Hints, member: Schema[A]) : F[List[A]] - def set[A](shapeId: ShapeId, hints: Hints, member: Schema[A]): F[Set[A]] + def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C, A], member: Schema[A]): F[C[A]] def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): F[Map[K, V]] def enumeration[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]) : F[E] def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S) : F[S] @@ -34,8 +33,7 @@ trait SchemaVisitor[F[_]] extends (Schema ~> F) { def apply[A](schema: Schema[A]) : F[A] = schema match { case PrimitiveSchema(shapeId, hints, tag) => primitive(shapeId, hints, tag) - case ListSchema(shapeId, hints, member) => list(shapeId, hints, member) - case SetSchema(shapeId, hints, member) => set(shapeId, hints, member) + case s: CollectionSchema[c, a] => collection[c,a](s.shapeId, s.hints, s.tag, s.member) case MapSchema(shapeId, hints, key, value) => map(shapeId, hints, key, value) case EnumerationSchema(shapeId, hints, values, total) => enumeration(shapeId, hints, values, total) case StructSchema(shapeId, hints, fields, make) => struct(shapeId, hints, fields, make) diff --git a/modules/core/src/smithy4s/schema/Schematic.scala b/modules/core/src/smithy4s/schema/Schematic.scala index 6b05af0b0..a23b04ad2 100644 --- a/modules/core/src/smithy4s/schema/Schematic.scala +++ b/modules/core/src/smithy4s/schema/Schematic.scala @@ -41,8 +41,7 @@ trait Schematic[F[_]] { def timestamp: F[Timestamp] // collections - def set[S](fs: F[S]): F[Set[S]] - def list[S](fs: F[S]): F[List[S]] + def collection[C[_], S](tag: CollectionTag[C, S], fs: F[S]): F[C[S]] def map[K, V](fk: F[K], fv: F[V]): F[Map[K, V]] // Other @@ -84,10 +83,8 @@ object Schematic { val fromOrdinal = values.map { v => v.ordinal -> v.value }.toMap val fromName = values.map { v => v.stringValue -> v.value }.toMap enumeration(to, fromName, fromOrdinal) - case SetSchema(_, _, member) => - set(apply(member)) - case ListSchema(_, _, member) => - list(apply(member)) + case s: CollectionSchema[c, a] => + collection[c, a](s.tag, apply(s.member)) case MapSchema(_, _, key, value) => map(apply(key), apply(value)) case BijectionSchema(underlying, to, from) => diff --git a/modules/core/src/smithy4s/schema/SchematicRepr.scala b/modules/core/src/smithy4s/schema/SchematicRepr.scala index 777c87b55..b064a3f75 100644 --- a/modules/core/src/smithy4s/schema/SchematicRepr.scala +++ b/modules/core/src/smithy4s/schema/SchematicRepr.scala @@ -45,9 +45,8 @@ object SchematicRepr extends Schematic[Repr] { def document: String = "document" - def list[S](fs: String): String = s"list[$fs]" - - def set[S](fs: String): String = s"set[$fs]" + def collection[C[_], S](tag: CollectionTag[C, S], fs: String): String = + s"collection[$fs]" def uuid: String = "uuid" diff --git a/modules/core/src/smithy4s/schema/StubSchematic.scala b/modules/core/src/smithy4s/schema/StubSchematic.scala index f4e3b389e..3e186f6ad 100644 --- a/modules/core/src/smithy4s/schema/StubSchematic.scala +++ b/modules/core/src/smithy4s/schema/StubSchematic.scala @@ -55,9 +55,10 @@ trait StubSchematic[F[_]] extends Schematic[F] { override def withHints[A](fa: F[A], hints: Hints): F[A] = default - override def list[S](fs: F[S]): F[List[S]] = default - - override def set[S](fs: F[S]): F[Set[S]] = default + override def collection[C[_], S]( + tag: CollectionTag[C, S], + fs: F[S] + ): F[C[S]] = default override def map[K, V](fk: F[K], fv: F[V]): F[Map[K, V]] = default diff --git a/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala b/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala index 1c5d52b9c..bd64e2b5b 100644 --- a/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala +++ b/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala @@ -36,7 +36,10 @@ class ShapeIdHintsSmokeSpec() extends munit.FunSuite { ): ToShapeIds[S] = fields.flatMap(_.instance).toList - override def list[S](fs: ToShapeIds[S]): ToShapeIds[List[S]] = fs + override def collection[C[_], S]( + tag: CollectionTag[C, S], + fs: ToShapeIds[S] + ): ToShapeIds[C[S]] = fs override def union[S]( first: Alt[ToShapeIds, S, _], diff --git a/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala b/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala index 56a1c971b..a20cdcd29 100644 --- a/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala +++ b/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala @@ -23,6 +23,7 @@ import smithy4s.Hints import smithy4s.Lazy import smithy4s.Schematic import smithy4s.Timestamp +import smithy4s.schema.CollectionTag import smithy4s.schema.Alt import smithy4s.schema.Field @@ -69,12 +70,10 @@ object FromMetadataSchematic extends Schematic[FromMetadata] { def document: FromMetadata[Document] = FromMetadata.default - def list[S](fs: FromMetadata[S]): FromMetadata[List[S]] = FromMetadata.default - - def set[S](fs: FromMetadata[S]): FromMetadata[Set[S]] = FromMetadata.default - - def vector[S](fs: FromMetadata[S]): FromMetadata[Vector[S]] = - FromMetadata.default + def collection[C[_], S]( + tag: CollectionTag[C, S], + fs: FromMetadata[S] + ): FromMetadata[C[S]] = FromMetadata.default def map[K, V]( fk: FromMetadata[K], From 52daa2dd5df7e791ece174fe35cc06a9a0a2d06d Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Mon, 4 Jul 2022 14:02:20 -0400 Subject: [PATCH 02/13] Fix errors throughout in other modules --- .../smithy4s/dynamic/DefaultSchematic.scala | 6 +- .../http/json/SchemaVisitorJCodec.scala | 132 ++++++------------ .../smithy4s/scalacheck/SchematicGen.scala | 16 ++- 3 files changed, 58 insertions(+), 96 deletions(-) diff --git a/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala b/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala index 87771b5e1..1e80c603a 100644 --- a/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala +++ b/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala @@ -19,6 +19,7 @@ package dynamic import cats.Id import java.util.UUID +import smithy4s.schema.CollectionTag import smithy4s.schema.Field import smithy4s.schema.Alt @@ -45,9 +46,8 @@ object DefaultSchematic extends smithy4s.Schematic[Id] { def unit: Id[Unit] = () - def list[S](fs: Id[S]): Id[List[S]] = List.empty - - def set[S](fs: Id[S]): Id[Set[S]] = Set.empty + def collection[C[_], S](tag: CollectionTag[C, S], fs: Id[S]): Id[C[S]] = + tag.empty def vector[S](fs: Id[S]): Id[Vector[S]] = Vector.empty diff --git a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala index 685cedfa0..835d8866c 100644 --- a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala @@ -35,7 +35,6 @@ import smithy4s.Timestamp import scala.collection.compat.immutable.ArraySeq import scala.collection.immutable.VectorBuilder -import scala.collection.mutable.ListBuffer import scala.collection.mutable.{Map => MMap} private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) @@ -429,101 +428,60 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) } } - private def listImpl[A](member: Schema[A]) = new JCodec[List[A]] { - private[this] val a: JCodec[A] = apply(member) - def expecting: String = "list" + private def listImpl[C[_], A](tag: CollectionTag[C, A], member: Schema[A]) = + new JCodec[C[A]] { + private[this] val a: JCodec[A] = apply(member) + def expecting: String = "list" - override def canBeKey: Boolean = false + override def canBeKey: Boolean = false - def decodeValue(cursor: Cursor, in: JsonReader): List[A] = - if (in.isNextToken('[')) { - if (in.isNextToken(']')) Nil - else { - in.rollbackToken() - val builder = new ListBuffer[A] - var i = 0 - while ({ - if (i >= maxArity) - throw cursor.payloadError( - this, - s"input $expecting exceeded max arity of `$maxArity`" - ) - builder += cursor.under(i)(cursor.decode(a, in)) - i += 1 - in.isNextToken(',') - }) () - if (in.isCurrentToken(']')) builder.result() - else in.arrayEndOrCommaError() + def decodeValue(cursor: Cursor, in: JsonReader): C[A] = + if (in.isNextToken('[')) { + if (in.isNextToken(']')) tag.empty + else { + in.rollbackToken() + tag.build { put => + var i = 0 + while ({ + if (i >= maxArity) + throw cursor.payloadError( + this, + s"input $expecting exceeded max arity of `$maxArity`" + ) + put(cursor.under(i)(cursor.decode(a, in))) + i += 1 + in.isNextToken(',') + }) () + + if (!in.isCurrentToken(']')) { + in.arrayEndOrCommaError() + } + } + + } + } else in.decodeError("Expected JSON array") + + def encodeValue(xs: C[A], out: JsonWriter): Unit = { + out.writeArrayStart() + tag.iterator(xs).foreach { v => + a.encodeValue(v, out) } - } else in.decodeError("Expected JSON array") - - def encodeValue(xs: List[A], out: JsonWriter): Unit = { - out.writeArrayStart() - var list = xs - while (list ne Nil) { - a.encodeValue(list.head, out) - list = list.tail + out.writeArrayEnd() } - out.writeArrayEnd() - } - def decodeKey(in: JsonReader): List[A] = - in.decodeError("Cannot use vectors as keys") + def decodeKey(in: JsonReader): C[A] = + in.decodeError("Cannot use vectors as keys") - def encodeKey(xs: List[A], out: JsonWriter): Unit = - out.encodeError("Cannot use vectors as keys") - } - - override def list[A]( - shapeId: ShapeId, - hints: Hints, - member: Schema[A] - ): JCodec[List[A]] = listImpl(member) + def encodeKey(xs: C[A], out: JsonWriter): Unit = + out.encodeError("Cannot use vectors as keys") + } - override def set[A]( + override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, + tag: CollectionTag[C, A], member: Schema[A] - ): JCodec[Set[A]] = new JCodec[Set[A]] { - private[this] val a = apply(member) - def expecting: String = "list" - - override def canBeKey: Boolean = false - - def decodeValue(cursor: Cursor, in: JsonReader): Set[A] = - if (in.isNextToken('[')) { - if (in.isNextToken(']')) Set.empty - else { - in.rollbackToken() - val builder = Set.newBuilder[A] - var i = 0 - while ({ - if (i >= maxArity) - throw cursor.payloadError( - this, - s"input $expecting exceeded max arity of `$maxArity`" - ) - builder += cursor.under(i)(cursor.decode(a, in)) - i += 1 - in.isNextToken(',') - }) () - if (in.isCurrentToken(']')) builder.result() - else in.arrayEndOrCommaError() - } - } else in.decodeError("Expected JSON array") - - def encodeValue(xs: Set[A], out: JsonWriter): Unit = { - out.writeArrayStart() - xs.foreach(x => a.encodeValue(x, out)) - out.writeArrayEnd() - } - - def decodeKey(in: JsonReader): Set[A] = - in.decodeError("Cannot use vectors as keys") - - def encodeKey(xs: Set[A], out: JsonWriter): Unit = - out.encodeError("Cannot use vectors as keys") - } + ): JCodec[C[A]] = listImpl(tag, member) private def objectMap[K, V]( jk: JCodec[K], @@ -587,7 +545,7 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) val kvCodec = Schema.struct(Vector(kField, vField))(vec => (vec(0).asInstanceOf[K], vec(1).asInstanceOf[V]) ) - listImpl(kvCodec).biject(_.toMap, _.toList) + listImpl(CollectionTag.list[(K, V)], kvCodec).biject(_.toMap, _.toList) } override def map[K, V]( diff --git a/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala b/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala index 80e64688a..60a135ca5 100644 --- a/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala +++ b/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala @@ -60,12 +60,16 @@ abstract class SchematicGen2 extends SchemaVisitor[Gen] { self => } } - def list[A](shapeId: ShapeId, hints: Hints, member: Schema[A]): Gen[List[A]] = - length(hints).flatMap(l => Gen.listOfN(l, member.compile(this))) - def set[A](shapeId: ShapeId, hints: Hints, member: Schema[A]): Gen[Set[A]] = - length(hints).flatMap(l => - Gen.listOfN(l, member.compile(this)).map(_.toSet) - ) + def collection[C[_], A]( + shapeId: ShapeId, + hints: Hints, + tag: CollectionTag[C, A], + member: Schema[A] + ): Gen[C[A]] = + length(hints) + .flatMap(l => Gen.listOfN(l, member.compile(this))) + .map(l => tag.fromIterator(l.iterator)) + def map[K, V]( shapeId: ShapeId, hints: Hints, From 6b6ecb85e2da232bf7db010b807507cd4045d708 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Tue, 5 Jul 2022 09:27:10 -0400 Subject: [PATCH 03/13] Improve CollectionTag Move `A` as a method param Add name: String on the interface --- .../SchemaVisitorMetadataReader.scala | 2 +- .../SchemaVisitorMetadataWriter.scala | 2 +- .../internals/SchemaVisitorPathEncoder.scala | 2 +- .../internals/SchematicDocumentDecoder.scala | 2 +- .../internals/SchematicDocumentEncoder.scala | 2 +- .../src/smithy4s/schema/CollectionTag.scala | 34 +++++++++++-------- .../schema/PassthroughSchematic.scala | 2 +- modules/core/src/smithy4s/schema/Schema.scala | 6 ++-- .../src/smithy4s/schema/SchemaVisitor.scala | 2 +- .../core/src/smithy4s/schema/Schematic.scala | 2 +- .../src/smithy4s/schema/SchematicRepr.scala | 2 +- .../src/smithy4s/schema/StubSchematic.scala | 2 +- .../src/smithy4s/ShapeIdHintsSmokeSpec.scala | 2 +- .../internals/FromMetadataSchematic.scala | 2 +- .../smithy4s/dynamic/DefaultSchematic.scala | 2 +- .../http/json/SchemaVisitorJCodec.scala | 6 ++-- .../smithy4s/scalacheck/SchematicGen.scala | 2 +- 17 files changed, 39 insertions(+), 35 deletions(-) diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala index ab9d8a38a..290eea856 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala @@ -101,7 +101,7 @@ private[http] class SchemaVisitorMetadataReader() override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, - tag: CollectionTag[C, A], + tag: CollectionTag[C], member: Schema[A] ): MetaDecode[C[A]] = { self(member) match { diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala index fdd4d7a45..bfd8957d1 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataWriter.scala @@ -88,7 +88,7 @@ object SchemaVisitorMetadataWriter extends SchemaVisitor[MetaEncode] { self => override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, - tag: CollectionTag[C, A], + tag: CollectionTag[C], member: Schema[A] ): MetaEncode[C[A]] = { self(member) match { diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala index 724d67aa2..8a01686e0 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala @@ -60,7 +60,7 @@ object SchemaVisitorPathEncoder extends SchemaVisitor[MaybePathEncode] { self => override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, - tag: CollectionTag[C, A], + tag: CollectionTag[C], member: Schema[A] ): MaybePathEncode[C[A]] = default diff --git a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala index 82aa2ff7b..6039bc33d 100644 --- a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala +++ b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala @@ -230,7 +230,7 @@ object SchematicDocumentDecoder extends Schematic[DocumentDecoderMake] { }) def collection[C[_], S]( - tag: CollectionTag[C, S], + tag: CollectionTag[C], fs: DocumentDecoderMake[S] ): DocumentDecoderMake[C[S]] = fs.transform { fa => diff --git a/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala b/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala index 79bc7b426..30363164a 100644 --- a/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala +++ b/modules/core/src/smithy4s/internals/SchematicDocumentEncoder.scala @@ -114,7 +114,7 @@ object SchematicDocumentEncoder extends Schematic[DocumentEncoderMake] { def unit: DocumentEncoderMake[Unit] = fromNotKey(_ => DObject(Map.empty)) def collection[C[_], S]( - tag: CollectionTag[C, S], + tag: CollectionTag[C], fs: DocumentEncoderMake[S] ): DocumentEncoderMake[C[S]] = fs.transform(encoderS => diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala index c994981f7..c06d291e1 100644 --- a/modules/core/src/smithy4s/schema/CollectionTag.scala +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -17,35 +17,39 @@ package smithy4s package schema -sealed trait CollectionTag[C[_], A] { - def iterator(c: C[A]): Iterator[A] - def build(put: (A => Unit) => Unit): C[A] +sealed trait CollectionTag[C[_]] { + def name: String - def fromIterator(it: Iterator[A]): C[A] = build(put => it.foreach(put(_))) - def empty: C[A] = build(_ => ()) + def iterator[A](c: C[A]): Iterator[A] + def build[A](put: (A => Unit) => Unit): C[A] + + def fromIterator[A](it: Iterator[A]): C[A] = build(put => it.foreach(put(_))) + def empty[A]: C[A] = build(_ => ()) } object CollectionTag { - def list[A]: CollectionTag[List, A] = new CollectionTag[List, A] { + import scala.collection.{immutable => cols} + + case object List extends CollectionTag[List] { + override def name: String = "List" - override def iterator(c: List[A]): Iterator[A] = c.iterator + override def iterator[A](c: List[A]): Iterator[A] = c.iterator - override def build(put: (A => Unit) => Unit): List[A] = { - val builder = List.newBuilder[A] + override def build[A](put: (A => Unit) => Unit): List[A] = { + val builder = cols.List.newBuilder[A] put(builder.+=(_)) builder.result() } } - def set[A]: CollectionTag[Set, A] = new CollectionTag[Set, A] { + case object Set extends CollectionTag[Set] { + override def name: String = "Set" + override def iterator[A](c: Set[A]): Iterator[A] = c.iterator - override def iterator(c: Set[A]): Iterator[A] = c.iterator - - override def build(put: (A => Unit) => Unit): Set[A] = { - val builder = Set.newBuilder[A] + override def build[A](put: (A => Unit) => Unit): Set[A] = { + val builder = cols.Set.newBuilder[A] put(builder.+=(_)) builder.result() } - } } diff --git a/modules/core/src/smithy4s/schema/PassthroughSchematic.scala b/modules/core/src/smithy4s/schema/PassthroughSchematic.scala index de4bd8d3d..1e4e7bcba 100644 --- a/modules/core/src/smithy4s/schema/PassthroughSchematic.scala +++ b/modules/core/src/smithy4s/schema/PassthroughSchematic.scala @@ -46,7 +46,7 @@ class PassthroughSchematic[F[_]](schematic: Schematic[F]) extends Schematic[F] { def unit: F[Unit] = schematic.unit - def collection[C[_], S](tag: CollectionTag[C, S], fs: F[S]): F[C[S]] = + def collection[C[_], S](tag: CollectionTag[C], fs: F[S]): F[C[S]] = schematic.collection(tag, fs) def map[K, V](fk: F[K], fv: F[V]): F[Map[K, V]] = schematic.map(fk, fv) diff --git a/modules/core/src/smithy4s/schema/Schema.scala b/modules/core/src/smithy4s/schema/Schema.scala index 59454717c..62eec579d 100644 --- a/modules/core/src/smithy4s/schema/Schema.scala +++ b/modules/core/src/smithy4s/schema/Schema.scala @@ -88,7 +88,7 @@ sealed trait Schema[A]{ object Schema { final case class PrimitiveSchema[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) extends Schema[P] - final case class CollectionSchema[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C, A], member: Schema[A]) extends Schema[C[A]] + final case class CollectionSchema[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]) extends Schema[C[A]] final case class MapSchema[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]) extends Schema[Map[K, V]] final case class EnumerationSchema[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]) extends Schema[E] final case class StructSchema[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S) extends Schema[S] @@ -137,8 +137,8 @@ object Schema { private val placeholder: ShapeId = ShapeId("placeholder", "Placeholder") - def list[A](a: Schema[A]): Schema[List[A]] = Schema.CollectionSchema[List, A](placeholder, Hints.empty, CollectionTag.list, a) - def set[A](a: Schema[A]): Schema[Set[A]] = Schema.CollectionSchema[Set, A](placeholder, Hints.empty, CollectionTag.set, a) + def list[A](a: Schema[A]): Schema[List[A]] = Schema.CollectionSchema[List, A](placeholder, Hints.empty, CollectionTag.List, a) + def set[A](a: Schema[A]): Schema[Set[A]] = Schema.CollectionSchema[Set, A](placeholder, Hints.empty, CollectionTag.Set, a) def map[K, V](k: Schema[K], v: Schema[V]): Schema[Map[K, V]] = Schema.MapSchema(placeholder, Hints.empty, k, v) def recursive[A](s : => Schema[A]) : Schema[A] = Schema.LazySchema(Lazy(s)) diff --git a/modules/core/src/smithy4s/schema/SchemaVisitor.scala b/modules/core/src/smithy4s/schema/SchemaVisitor.scala index 4c00be70d..5f970bcef 100644 --- a/modules/core/src/smithy4s/schema/SchemaVisitor.scala +++ b/modules/core/src/smithy4s/schema/SchemaVisitor.scala @@ -22,7 +22,7 @@ import Schema._ // format: off trait SchemaVisitor[F[_]] extends (Schema ~> F) { def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) : F[P] - def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C, A], member: Schema[A]): F[C[A]] + def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]): F[C[A]] def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): F[Map[K, V]] def enumeration[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]) : F[E] def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S) : F[S] diff --git a/modules/core/src/smithy4s/schema/Schematic.scala b/modules/core/src/smithy4s/schema/Schematic.scala index a23b04ad2..81d7c2ae4 100644 --- a/modules/core/src/smithy4s/schema/Schematic.scala +++ b/modules/core/src/smithy4s/schema/Schematic.scala @@ -41,7 +41,7 @@ trait Schematic[F[_]] { def timestamp: F[Timestamp] // collections - def collection[C[_], S](tag: CollectionTag[C, S], fs: F[S]): F[C[S]] + def collection[C[_], S](tag: CollectionTag[C], fs: F[S]): F[C[S]] def map[K, V](fk: F[K], fv: F[V]): F[Map[K, V]] // Other diff --git a/modules/core/src/smithy4s/schema/SchematicRepr.scala b/modules/core/src/smithy4s/schema/SchematicRepr.scala index b064a3f75..3e2418298 100644 --- a/modules/core/src/smithy4s/schema/SchematicRepr.scala +++ b/modules/core/src/smithy4s/schema/SchematicRepr.scala @@ -45,7 +45,7 @@ object SchematicRepr extends Schematic[Repr] { def document: String = "document" - def collection[C[_], S](tag: CollectionTag[C, S], fs: String): String = + def collection[C[_], S](tag: CollectionTag[C], fs: String): String = s"collection[$fs]" def uuid: String = "uuid" diff --git a/modules/core/src/smithy4s/schema/StubSchematic.scala b/modules/core/src/smithy4s/schema/StubSchematic.scala index 3e186f6ad..8be06e4a8 100644 --- a/modules/core/src/smithy4s/schema/StubSchematic.scala +++ b/modules/core/src/smithy4s/schema/StubSchematic.scala @@ -56,7 +56,7 @@ trait StubSchematic[F[_]] extends Schematic[F] { override def withHints[A](fa: F[A], hints: Hints): F[A] = default override def collection[C[_], S]( - tag: CollectionTag[C, S], + tag: CollectionTag[C], fs: F[S] ): F[C[S]] = default diff --git a/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala b/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala index bd64e2b5b..2f51b6e8e 100644 --- a/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala +++ b/modules/core/test/src/smithy4s/ShapeIdHintsSmokeSpec.scala @@ -37,7 +37,7 @@ class ShapeIdHintsSmokeSpec() extends munit.FunSuite { fields.flatMap(_.instance).toList override def collection[C[_], S]( - tag: CollectionTag[C, S], + tag: CollectionTag[C], fs: ToShapeIds[S] ): ToShapeIds[C[S]] = fs diff --git a/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala b/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala index a20cdcd29..a979de102 100644 --- a/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala +++ b/modules/core/test/src/smithy4s/http/internals/FromMetadataSchematic.scala @@ -71,7 +71,7 @@ object FromMetadataSchematic extends Schematic[FromMetadata] { def document: FromMetadata[Document] = FromMetadata.default def collection[C[_], S]( - tag: CollectionTag[C, S], + tag: CollectionTag[C], fs: FromMetadata[S] ): FromMetadata[C[S]] = FromMetadata.default diff --git a/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala b/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala index 1e80c603a..f166de190 100644 --- a/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala +++ b/modules/dynamic/test/src/smithy4s/dynamic/DefaultSchematic.scala @@ -46,7 +46,7 @@ object DefaultSchematic extends smithy4s.Schematic[Id] { def unit: Id[Unit] = () - def collection[C[_], S](tag: CollectionTag[C, S], fs: Id[S]): Id[C[S]] = + def collection[C[_], S](tag: CollectionTag[C], fs: Id[S]): Id[C[S]] = tag.empty def vector[S](fs: Id[S]): Id[Vector[S]] = Vector.empty diff --git a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala index 835d8866c..645b0ec5d 100644 --- a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala @@ -428,7 +428,7 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) } } - private def listImpl[C[_], A](tag: CollectionTag[C, A], member: Schema[A]) = + private def listImpl[C[_], A](tag: CollectionTag[C], member: Schema[A]) = new JCodec[C[A]] { private[this] val a: JCodec[A] = apply(member) def expecting: String = "list" @@ -479,7 +479,7 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) override def collection[C[_], A]( shapeId: ShapeId, hints: Hints, - tag: CollectionTag[C, A], + tag: CollectionTag[C], member: Schema[A] ): JCodec[C[A]] = listImpl(tag, member) @@ -545,7 +545,7 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) val kvCodec = Schema.struct(Vector(kField, vField))(vec => (vec(0).asInstanceOf[K], vec(1).asInstanceOf[V]) ) - listImpl(CollectionTag.list[(K, V)], kvCodec).biject(_.toMap, _.toList) + listImpl(CollectionTag.List, kvCodec).biject(_.toMap, _.toList) } override def map[K, V]( diff --git a/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala b/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala index 60a135ca5..fe7f71e2e 100644 --- a/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala +++ b/modules/scalacheck/src/smithy4s/scalacheck/SchematicGen.scala @@ -63,7 +63,7 @@ abstract class SchematicGen2 extends SchemaVisitor[Gen] { self => def collection[C[_], A]( shapeId: ShapeId, hints: Hints, - tag: CollectionTag[C, A], + tag: CollectionTag[C], member: Schema[A] ): Gen[C[A]] = length(hints) From 465829f48fb0e183219241ba052a001b96fa41b8 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Tue, 5 Jul 2022 09:49:18 -0400 Subject: [PATCH 04/13] Add Vector and ArraySeq implementation --- .../src/smithy4s/schema/CollectionTag.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala index c06d291e1..372f388e2 100644 --- a/modules/core/src/smithy4s/schema/CollectionTag.scala +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -42,6 +42,7 @@ object CollectionTag { } } + case object Set extends CollectionTag[Set] { override def name: String = "Set" override def iterator[A](c: Set[A]): Iterator[A] = c.iterator @@ -52,4 +53,27 @@ object CollectionTag { builder.result() } } + + case object Vector extends CollectionTag[Vector] { + override def name: String = "Vector" + override def iterator[A](c: Vector[A]): Iterator[A] = c.iterator + + override def build[A](put: (A => Unit) => Unit): Vector[A] = { + val builder = cols.Vector.newBuilder[A] + put(builder.+=(_)) + builder.result() + } + } + + case object ArraySeq extends CollectionTag[cols.ArraySeq] { + override def name: String = "ArraySeq" + override def iterator[A](c: cols.ArraySeq[A]): Iterator[A] = c.iterator + + override def build[A](put: (A => Unit) => Unit): cols.ArraySeq[A] = { + // we're using any here, to avoid the `ClassTag` constraints of `newBuilder` + val builder = cols.ArraySeq.newBuilder[Any] + put(builder.+=(_)) + builder.result().asInstanceOf[cols.ArraySeq[A]] + } + } } From 147f72803e11f9ea5bf8d5b16a2302df0a5dfeb2 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Tue, 5 Jul 2022 09:56:42 -0400 Subject: [PATCH 05/13] Remove ArraySeq for now --- modules/core/src/smithy4s/schema/CollectionTag.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala index 372f388e2..9e34e3a20 100644 --- a/modules/core/src/smithy4s/schema/CollectionTag.scala +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -64,16 +64,4 @@ object CollectionTag { builder.result() } } - - case object ArraySeq extends CollectionTag[cols.ArraySeq] { - override def name: String = "ArraySeq" - override def iterator[A](c: cols.ArraySeq[A]): Iterator[A] = c.iterator - - override def build[A](put: (A => Unit) => Unit): cols.ArraySeq[A] = { - // we're using any here, to avoid the `ClassTag` constraints of `newBuilder` - val builder = cols.ArraySeq.newBuilder[Any] - put(builder.+=(_)) - builder.result().asInstanceOf[cols.ArraySeq[A]] - } - } } From 9a4a772196dc8fe522e90a1bae7f38d2302bddcf Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Tue, 5 Jul 2022 09:56:58 -0400 Subject: [PATCH 06/13] Use tag.name in string representation --- .../core/src/smithy4s/internals/SchematicDocumentDecoder.scala | 2 +- modules/core/src/smithy4s/schema/SchematicRepr.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala index 6039bc33d..c6d7c52f9 100644 --- a/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala +++ b/modules/core/src/smithy4s/internals/SchematicDocumentDecoder.scala @@ -234,7 +234,7 @@ object SchematicDocumentDecoder extends Schematic[DocumentDecoderMake] { fs: DocumentDecoderMake[S] ): DocumentDecoderMake[C[S]] = fs.transform { fa => - DocumentDecoder.instance("Collection", "Array", false) { + DocumentDecoder.instance(tag.name, "Array", false) { case (pp, DArray(value)) => tag.fromIterator(value.iterator.zipWithIndex.map { case (document, index) => diff --git a/modules/core/src/smithy4s/schema/SchematicRepr.scala b/modules/core/src/smithy4s/schema/SchematicRepr.scala index 3e2418298..192ec565e 100644 --- a/modules/core/src/smithy4s/schema/SchematicRepr.scala +++ b/modules/core/src/smithy4s/schema/SchematicRepr.scala @@ -46,7 +46,7 @@ object SchematicRepr extends Schematic[Repr] { def document: String = "document" def collection[C[_], S](tag: CollectionTag[C], fs: String): String = - s"collection[$fs]" + s"${tag.name}[$fs]" def uuid: String = "uuid" From df05035a3627b25cc6da826874e24c66a3b592d8 Mon Sep 17 00:00:00 2001 From: David Francoeur Date: Tue, 5 Jul 2022 10:23:20 -0400 Subject: [PATCH 07/13] Rework implementation for jcodec collections --- .../http/json/SchemaVisitorJCodec.scala | 180 +++++++++++++----- 1 file changed, 135 insertions(+), 45 deletions(-) diff --git a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala index 645b0ec5d..75c5fcddc 100644 --- a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala @@ -35,6 +35,7 @@ import smithy4s.Timestamp import scala.collection.compat.immutable.ArraySeq import scala.collection.immutable.VectorBuilder +import scala.collection.mutable.ListBuffer import scala.collection.mutable.{Map => MMap} private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) @@ -428,60 +429,136 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) } } - private def listImpl[C[_], A](tag: CollectionTag[C], member: Schema[A]) = - new JCodec[C[A]] { - private[this] val a: JCodec[A] = apply(member) - def expecting: String = "list" + private def listImpl[A](member: Schema[A]) = new JCodec[List[A]] { + private[this] val a: JCodec[A] = apply(member) + def expecting: String = "list" - override def canBeKey: Boolean = false + override def canBeKey: Boolean = false - def decodeValue(cursor: Cursor, in: JsonReader): C[A] = - if (in.isNextToken('[')) { - if (in.isNextToken(']')) tag.empty - else { - in.rollbackToken() - tag.build { put => - var i = 0 - while ({ - if (i >= maxArity) - throw cursor.payloadError( - this, - s"input $expecting exceeded max arity of `$maxArity`" - ) - put(cursor.under(i)(cursor.decode(a, in))) - i += 1 - in.isNextToken(',') - }) () + def decodeValue(cursor: Cursor, in: JsonReader): List[A] = + if (in.isNextToken('[')) { + if (in.isNextToken(']')) Nil + else { + in.rollbackToken() + val builder = new ListBuffer[A] + var i = 0 + while ({ + if (i >= maxArity) + throw cursor.payloadError( + this, + s"input $expecting exceeded max arity of `$maxArity`" + ) + builder += cursor.under(i)(cursor.decode(a, in)) + i += 1 + in.isNextToken(',') + }) () + if (in.isCurrentToken(']')) builder.result() + else in.arrayEndOrCommaError() + } + } else in.decodeError("Expected JSON array") + + def encodeValue(xs: List[A], out: JsonWriter): Unit = { + out.writeArrayStart() + var list = xs + while (list ne Nil) { + a.encodeValue(list.head, out) + list = list.tail + } + out.writeArrayEnd() + } - if (!in.isCurrentToken(']')) { - in.arrayEndOrCommaError() - } - } + def decodeKey(in: JsonReader): List[A] = + in.decodeError("Cannot use vectors as keys") - } - } else in.decodeError("Expected JSON array") + def encodeKey(xs: List[A], out: JsonWriter): Unit = + out.encodeError("Cannot use vectors as keys") + } - def encodeValue(xs: C[A], out: JsonWriter): Unit = { - out.writeArrayStart() - tag.iterator(xs).foreach { v => - a.encodeValue(v, out) - } - out.writeArrayEnd() - } + private def vector[A]( + member: Schema[A] + ): JCodec[Vector[A]] = new JCodec[Vector[A]] { + private[this] val a = apply(member) + def expecting: String = "list" - def decodeKey(in: JsonReader): C[A] = - in.decodeError("Cannot use vectors as keys") + override def canBeKey: Boolean = false + + def decodeValue(cursor: Cursor, in: JsonReader): Vector[A] = + if (in.isNextToken('[')) { + if (in.isNextToken(']')) Vector.empty + else { + in.rollbackToken() + val builder = Vector.newBuilder[A] + var i = 0 + while ({ + if (i >= maxArity) + throw cursor.payloadError( + this, + s"input $expecting exceeded max arity of `$maxArity`" + ) + builder += cursor.under(i)(cursor.decode(a, in)) + i += 1 + in.isNextToken(',') + }) () + if (in.isCurrentToken(']')) builder.result() + else in.arrayEndOrCommaError() + } + } else in.decodeError("Expected JSON array") - def encodeKey(xs: C[A], out: JsonWriter): Unit = - out.encodeError("Cannot use vectors as keys") + def encodeValue(xs: Vector[A], out: JsonWriter): Unit = { + out.writeArrayStart() + xs.foreach(x => a.encodeValue(x, out)) + out.writeArrayEnd() } - override def collection[C[_], A]( - shapeId: ShapeId, - hints: Hints, - tag: CollectionTag[C], + def decodeKey(in: JsonReader): Vector[A] = + in.decodeError("Cannot use vectors as keys") + + def encodeKey(xs: Vector[A], out: JsonWriter): Unit = + out.encodeError("Cannot use vectors as keys") + } + + private def set[A]( member: Schema[A] - ): JCodec[C[A]] = listImpl(tag, member) + ): JCodec[Set[A]] = new JCodec[Set[A]] { + private[this] val a = apply(member) + def expecting: String = "list" + + override def canBeKey: Boolean = false + + def decodeValue(cursor: Cursor, in: JsonReader): Set[A] = + if (in.isNextToken('[')) { + if (in.isNextToken(']')) Set.empty + else { + in.rollbackToken() + val builder = Set.newBuilder[A] + var i = 0 + while ({ + if (i >= maxArity) + throw cursor.payloadError( + this, + s"input $expecting exceeded max arity of `$maxArity`" + ) + builder += cursor.under(i)(cursor.decode(a, in)) + i += 1 + in.isNextToken(',') + }) () + if (in.isCurrentToken(']')) builder.result() + else in.arrayEndOrCommaError() + } + } else in.decodeError("Expected JSON array") + + def encodeValue(xs: Set[A], out: JsonWriter): Unit = { + out.writeArrayStart() + xs.foreach(x => a.encodeValue(x, out)) + out.writeArrayEnd() + } + + def decodeKey(in: JsonReader): Set[A] = + in.decodeError("Cannot use vectors as keys") + + def encodeKey(xs: Set[A], out: JsonWriter): Unit = + out.encodeError("Cannot use vectors as keys") + } private def objectMap[K, V]( jk: JCodec[K], @@ -545,7 +622,20 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) val kvCodec = Schema.struct(Vector(kField, vField))(vec => (vec(0).asInstanceOf[K], vec(1).asInstanceOf[V]) ) - listImpl(CollectionTag.List, kvCodec).biject(_.toMap, _.toList) + listImpl(kvCodec).biject(_.toMap, _.toList) + } + + override def collection[C[_], A]( + shapeId: ShapeId, + hints: Hints, + tag: CollectionTag[C], + member: Schema[A] + ): JCodec[C[A]] = { + tag match { + case CollectionTag.List => listImpl(member) + case CollectionTag.Set => set(member) + case CollectionTag.Vector => vector(member) + } } override def map[K, V]( From ec50c658fa69ca41a0d4358dfd047172b12508d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Wed, 6 Jul 2022 10:29:51 +0200 Subject: [PATCH 08/13] Add IndexedSeq to CollectionTag --- .../src/smithy4s/codegen/Renderer.scala | 2 +- modules/core/src-2/Newtype.scala | 5 ++ .../src/smithy4s/schema/CollectionTag.scala | 81 ++++++++++++++++--- modules/core/src/smithy4s/schema/Schema.scala | 7 +- .../http/json/SchemaVisitorJCodec.scala | 52 +++++++++++- .../smithy4s/scalacheck/SchemaGenerator.scala | 2 + 6 files changed, 134 insertions(+), 15 deletions(-) diff --git a/modules/codegen/src/smithy4s/codegen/Renderer.scala b/modules/codegen/src/smithy4s/codegen/Renderer.scala index 703d76e43..90c71d162 100644 --- a/modules/codegen/src/smithy4s/codegen/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/Renderer.scala @@ -597,7 +597,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self => renderHintsVal(hints), line"val underlyingSchema : $Schema_[$tpe] = ${tpe.schemaRef}$trailingCalls", lines( - s"implicit val schema : $Schema_[$name] = bijection(underlyingSchema, $name(_), (_ : $name).value)" + s"implicit val schema : $Schema_[$name] = bijection(underlyingSchema, $name.make, (_ : $name).value)" ) ) ).addImports(imports) diff --git a/modules/core/src-2/Newtype.scala b/modules/core/src-2/Newtype.scala index 5f04bc943..9ef117373 100644 --- a/modules/core/src-2/Newtype.scala +++ b/modules/core/src-2/Newtype.scala @@ -24,6 +24,7 @@ abstract class Newtype[A] extends HasId { self => type Type <: Base with _Tag @inline final def apply(a: A): Type = a.asInstanceOf[Type] + final val make: Newtype.Make[A, Type] = apply(_: A) @inline final def value(x: Type): A = x.asInstanceOf[A] @@ -51,3 +52,7 @@ abstract class Newtype[A] extends HasId { self => def unapply(h: Hints): Option[Type] = h.get(tag) } } + +object Newtype { + trait Make[A, B] extends Function[A, B] +} diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala index 9e34e3a20..e9ccc6930 100644 --- a/modules/core/src/smithy4s/schema/CollectionTag.scala +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -17,6 +17,8 @@ package smithy4s package schema +import scala.reflect.ClassTag + sealed trait CollectionTag[C[_]] { def name: String @@ -28,40 +30,101 @@ sealed trait CollectionTag[C[_]] { } object CollectionTag { - import scala.collection.{immutable => cols} + import scala.collection.compat.{immutable => cols} + import scala.collection.{mutable => mut} - case object List extends CollectionTag[List] { + case object ListTag extends CollectionTag[List] { override def name: String = "List" override def iterator[A](c: List[A]): Iterator[A] = c.iterator override def build[A](put: (A => Unit) => Unit): List[A] = { - val builder = cols.List.newBuilder[A] - put(builder.+=(_)) - builder.result() + val builder = mut.ListBuffer.newBuilder[A] + put(builder += (_)) + builder.result().toList } } - case object Set extends CollectionTag[Set] { + case object SetTag extends CollectionTag[Set] { override def name: String = "Set" override def iterator[A](c: Set[A]): Iterator[A] = c.iterator override def build[A](put: (A => Unit) => Unit): Set[A] = { - val builder = cols.Set.newBuilder[A] + val builder = Set.newBuilder[A] put(builder.+=(_)) builder.result() } } - case object Vector extends CollectionTag[Vector] { + case object VectorTag extends CollectionTag[Vector] { override def name: String = "Vector" override def iterator[A](c: Vector[A]): Iterator[A] = c.iterator override def build[A](put: (A => Unit) => Unit): Vector[A] = { - val builder = cols.Vector.newBuilder[A] + val builder = Vector.newBuilder[A] put(builder.+=(_)) builder.result() } } + + case object IndexedSeqTag extends CollectionTag[IndexedSeq] { + + override def name: String = "IndexedSeq" + override def iterator[A](c: IndexedSeq[A]): Iterator[A] = c.iterator + + override def build[A](put: (A => Unit) => Unit): Vector[A] = { + val builder = Vector.newBuilder[A] + put(builder.+=(_)) + builder.result() + } + + /** + * Returns a builder that may be able to store the elements in an unboxed + * fashion + */ + private[smithy4s] def compactBuilder[A]( + schema: Schema[A] + ): ((A => Unit) => Unit) => IndexedSeq[A] = + schema.compile(CTSchemaVisitor) match { + case Some(ct) => { (put: (A => Unit) => Unit) => + val builder = cols.ArraySeq.newBuilder(ct) + put(builder.+=(_)) + builder.result() + } + case None => { (put: (A => Unit) => Unit) => + val builder = IndexedSeq.newBuilder[A] + put(builder.+=(_)) + builder.result() + } + } + } + + private[this] type MaybeCT[A] = Option[ClassTag[A]] + + /** + * Retrieves a ClassTag whenever possible. + */ + // format: off + private[this] object CTSchemaVisitor extends SchemaVisitor[MaybeCT]{ + val primitiveCT = Primitive.deriving[ClassTag] + def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]): MaybeCT[P] = Some(primitiveCT(tag)) + def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]): MaybeCT[C[A]] = tag match { + case ListTag => Some(implicitly[ClassTag[List[A]]]) + case SetTag => Some(implicitly[ClassTag[Set[A]]]) + case VectorTag => Some(implicitly[ClassTag[Vector[A]]]) + case IndexedSeqTag => Some(implicitly[ClassTag[IndexedSeq[A]]]) + } + def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): MaybeCT[Map[K,V]] = Some(implicitly[ClassTag[Map[K, V]]]) + def enumeration[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]): MaybeCT[E] = None + def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S): MaybeCT[S] = None + def union[U](shapeId: ShapeId, hints: Hints, alternatives: Vector[SchemaAlt[U, _]], dispatch: U => Alt.SchemaAndValue[U, _]): MaybeCT[U] = None + def biject[A, B](schema: Schema[A], to: A => B, from: B => A): MaybeCT[B] = { + if (to.isInstanceOf[Newtype.Make[A, B]]) apply(schema).asInstanceOf[MaybeCT[B]] + else None + } + def surject[A, B](schema: Schema[A], to: Refinement[A,B], from: B => A): MaybeCT[B] = None + def lazily[A](suspend: Lazy[Schema[A]]): MaybeCT[A] = None + } + // format: off } diff --git a/modules/core/src/smithy4s/schema/Schema.scala b/modules/core/src/smithy4s/schema/Schema.scala index 62eec579d..f3ff084f0 100644 --- a/modules/core/src/smithy4s/schema/Schema.scala +++ b/modules/core/src/smithy4s/schema/Schema.scala @@ -137,8 +137,11 @@ object Schema { private val placeholder: ShapeId = ShapeId("placeholder", "Placeholder") - def list[A](a: Schema[A]): Schema[List[A]] = Schema.CollectionSchema[List, A](placeholder, Hints.empty, CollectionTag.List, a) - def set[A](a: Schema[A]): Schema[Set[A]] = Schema.CollectionSchema[Set, A](placeholder, Hints.empty, CollectionTag.Set, a) + def list[A](a: Schema[A]): Schema[List[A]] = Schema.CollectionSchema[List, A](placeholder, Hints.empty, CollectionTag.ListTag, a) + def set[A](a: Schema[A]): Schema[Set[A]] = Schema.CollectionSchema[Set, A](placeholder, Hints.empty, CollectionTag.SetTag, a) + def vector[A](a: Schema[A]): Schema[Vector[A]] = Schema.CollectionSchema[Vector, A](placeholder, Hints.empty, CollectionTag.VectorTag, a) + def indexedSeq[A](a: Schema[A]): Schema[IndexedSeq[A]] = Schema.CollectionSchema[IndexedSeq, A](placeholder, Hints.empty, CollectionTag.IndexedSeqTag, a) + def map[K, V](k: Schema[K], v: Schema[V]): Schema[Map[K, V]] = Schema.MapSchema(placeholder, Hints.empty, k, v) def recursive[A](s : => Schema[A]) : Schema[A] = Schema.LazySchema(Lazy(s)) diff --git a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala index 75c5fcddc..310a591ae 100644 --- a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala @@ -517,6 +517,51 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) out.encodeError("Cannot use vectors as keys") } + private def indexedSeq[A]( + member: Schema[A] + ): JCodec[IndexedSeq[A]] = new JCodec[IndexedSeq[A]] { + private[this] val a = apply(member) + def expecting: String = "list" + + override def canBeKey: Boolean = false + + def decodeValue(cursor: Cursor, in: JsonReader): IndexedSeq[A] = + if (in.isNextToken('[')) { + if (in.isNextToken(']')) Vector.empty + else { + in.rollbackToken() + CollectionTag.IndexedSeqTag.compactBuilder(member) { put => + var i = 0 + while ({ + if (i >= maxArity) + throw cursor.payloadError( + this, + s"input $expecting exceeded max arity of `$maxArity`" + ) + put(cursor.under(i)(cursor.decode(a, in))) + i += 1 + in.isNextToken(',') + }) () + if (!in.isCurrentToken(']')) { + in.arrayEndOrCommaError() + } + } + } + } else in.decodeError("Expected JSON array") + + def encodeValue(xs: IndexedSeq[A], out: JsonWriter): Unit = { + out.writeArrayStart() + xs.foreach(x => a.encodeValue(x, out)) + out.writeArrayEnd() + } + + def decodeKey(in: JsonReader): IndexedSeq[A] = + in.decodeError("Cannot use vectors as keys") + + def encodeKey(xs: IndexedSeq[A], out: JsonWriter): Unit = + out.encodeError("Cannot use vectors as keys") + } + private def set[A]( member: Schema[A] ): JCodec[Set[A]] = new JCodec[Set[A]] { @@ -632,9 +677,10 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) member: Schema[A] ): JCodec[C[A]] = { tag match { - case CollectionTag.List => listImpl(member) - case CollectionTag.Set => set(member) - case CollectionTag.Vector => vector(member) + case CollectionTag.ListTag => listImpl(member) + case CollectionTag.SetTag => set(member) + case CollectionTag.VectorTag => vector(member) + case CollectionTag.IndexedSeqTag => indexedSeq(member) } } diff --git a/modules/scalacheck/src/smithy4s/scalacheck/SchemaGenerator.scala b/modules/scalacheck/src/smithy4s/scalacheck/SchemaGenerator.scala index 2cc18df78..0cedb94b5 100644 --- a/modules/scalacheck/src/smithy4s/scalacheck/SchemaGenerator.scala +++ b/modules/scalacheck/src/smithy4s/scalacheck/SchemaGenerator.scala @@ -102,6 +102,8 @@ class SchemaGenerator(maxWidth: Int) { Vector( recurse.map(Schema.list(_)), recurse.map(Schema.set(_)), + recurse.map(Schema.vector(_)), + recurse.map(Schema.indexedSeq(_)), Gen.zip(recurse, recurse).map { case (k, v) => map(k, v) }, genStruct, genUnion From eefd49c3d545eca34e0bf7e38eb33089285d20ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Wed, 6 Jul 2022 10:34:57 +0200 Subject: [PATCH 09/13] pre-compute builder-making function --- modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala index 310a591ae..e3dfba91d 100644 --- a/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/http/json/SchemaVisitorJCodec.scala @@ -525,12 +525,14 @@ private[smithy4s] class SchemaVisitorJCodec(maxArity: Int) override def canBeKey: Boolean = false + val withBuilder = CollectionTag.IndexedSeqTag.compactBuilder(member) + def decodeValue(cursor: Cursor, in: JsonReader): IndexedSeq[A] = if (in.isNextToken('[')) { if (in.isNextToken(']')) Vector.empty else { in.rollbackToken() - CollectionTag.IndexedSeqTag.compactBuilder(member) { put => + withBuilder { put => var i = 0 while ({ if (i >= maxArity) From c6f454e5139b3aa4b415407000bcb7001b33b721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Wed, 6 Jul 2022 10:37:37 +0200 Subject: [PATCH 10/13] Fix Scala 3 --- modules/core/src-3/Newtype.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/core/src-3/Newtype.scala b/modules/core/src-3/Newtype.scala index 2d529a802..b6f051c38 100644 --- a/modules/core/src-3/Newtype.scala +++ b/modules/core/src-3/Newtype.scala @@ -20,6 +20,7 @@ abstract class Newtype[A] extends HasId { self => opaque type Type = A def apply(a: A): Type = a + final val make: Newtype.Make[A, Type] = apply(_: A) extension (orig: Type) def value: A = orig @@ -42,3 +43,7 @@ abstract class Newtype[A] extends HasId { self => def unapply(h: Hints): Option[Type] = h.get(tag) } } + +object Newtype { + trait Make[A, B] extends Function[A, B] +} From 26aa019c3d8a37c6e0ed374767770cb722ae2cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Wed, 6 Jul 2022 11:51:21 +0200 Subject: [PATCH 11/13] Fix cast --- modules/core/src/smithy4s/schema/CollectionTag.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/smithy4s/schema/CollectionTag.scala b/modules/core/src/smithy4s/schema/CollectionTag.scala index e9ccc6930..49f41046c 100644 --- a/modules/core/src/smithy4s/schema/CollectionTag.scala +++ b/modules/core/src/smithy4s/schema/CollectionTag.scala @@ -120,7 +120,7 @@ object CollectionTag { def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S): MaybeCT[S] = None def union[U](shapeId: ShapeId, hints: Hints, alternatives: Vector[SchemaAlt[U, _]], dispatch: U => Alt.SchemaAndValue[U, _]): MaybeCT[U] = None def biject[A, B](schema: Schema[A], to: A => B, from: B => A): MaybeCT[B] = { - if (to.isInstanceOf[Newtype.Make[A, B]]) apply(schema).asInstanceOf[MaybeCT[B]] + if (to.isInstanceOf[Newtype.Make[_, _]]) apply(schema).asInstanceOf[MaybeCT[B]] else None } def surject[A, B](schema: Schema[A], to: Refinement[A,B], from: B => A): MaybeCT[B] = None From b36408146072d32649cd6cc3aaa2158373fab302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Wed, 6 Jul 2022 12:08:21 +0200 Subject: [PATCH 12/13] Regenerated examples --- modules/example/src/smithy4s/example/ArbitraryData.scala | 2 +- modules/example/src/smithy4s/example/BucketName.scala | 2 +- modules/example/src/smithy4s/example/ObjectKey.scala | 2 +- modules/example/src/smithy4s/example/ObjectSize.scala | 2 +- modules/example/src/smithy4s/example/OrderNumber.scala | 2 +- modules/example/src/smithy4s/example/SomeValue.scala | 2 +- modules/example/src/smithy4s/example/StreamedBlob.scala | 2 +- modules/example/src/smithy4s/example/TestString.scala | 2 +- modules/example/src/smithy4s/example/common/BrandList.scala | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/example/src/smithy4s/example/ArbitraryData.scala b/modules/example/src/smithy4s/example/ArbitraryData.scala index 1a3dc812b..11b8ad95d 100644 --- a/modules/example/src/smithy4s/example/ArbitraryData.scala +++ b/modules/example/src/smithy4s/example/ArbitraryData.scala @@ -10,5 +10,5 @@ object ArbitraryData extends Newtype[Document] { smithy.api.Trait(None, None, None), ) val underlyingSchema : smithy4s.Schema[Document] = document.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[ArbitraryData] = bijection(underlyingSchema, ArbitraryData(_), (_ : ArbitraryData).value) + implicit val schema : smithy4s.Schema[ArbitraryData] = bijection(underlyingSchema, ArbitraryData.make, (_ : ArbitraryData).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/BucketName.scala b/modules/example/src/smithy4s/example/BucketName.scala index be14857dc..b8e84efa0 100644 --- a/modules/example/src/smithy4s/example/BucketName.scala +++ b/modules/example/src/smithy4s/example/BucketName.scala @@ -7,5 +7,5 @@ object BucketName extends Newtype[String] { val id: smithy4s.ShapeId = smithy4s.ShapeId("smithy4s.example", "BucketName") val hints : smithy4s.Hints = smithy4s.Hints.empty val underlyingSchema : smithy4s.Schema[String] = string.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[BucketName] = bijection(underlyingSchema, BucketName(_), (_ : BucketName).value) + implicit val schema : smithy4s.Schema[BucketName] = bijection(underlyingSchema, BucketName.make, (_ : BucketName).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/ObjectKey.scala b/modules/example/src/smithy4s/example/ObjectKey.scala index 389d7c908..79bcba607 100644 --- a/modules/example/src/smithy4s/example/ObjectKey.scala +++ b/modules/example/src/smithy4s/example/ObjectKey.scala @@ -10,5 +10,5 @@ object ObjectKey extends Newtype[UUID] { smithy4s.api.UuidFormat(), ) val underlyingSchema : smithy4s.Schema[UUID] = uuid.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[ObjectKey] = bijection(underlyingSchema, ObjectKey(_), (_ : ObjectKey).value) + implicit val schema : smithy4s.Schema[ObjectKey] = bijection(underlyingSchema, ObjectKey.make, (_ : ObjectKey).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/ObjectSize.scala b/modules/example/src/smithy4s/example/ObjectSize.scala index 7d217c9b7..4e817c381 100644 --- a/modules/example/src/smithy4s/example/ObjectSize.scala +++ b/modules/example/src/smithy4s/example/ObjectSize.scala @@ -7,5 +7,5 @@ object ObjectSize extends Newtype[Int] { val id: smithy4s.ShapeId = smithy4s.ShapeId("smithy4s.example", "ObjectSize") val hints : smithy4s.Hints = smithy4s.Hints.empty val underlyingSchema : smithy4s.Schema[Int] = int.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[ObjectSize] = bijection(underlyingSchema, ObjectSize(_), (_ : ObjectSize).value) + implicit val schema : smithy4s.Schema[ObjectSize] = bijection(underlyingSchema, ObjectSize.make, (_ : ObjectSize).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/OrderNumber.scala b/modules/example/src/smithy4s/example/OrderNumber.scala index 517825b84..f5240928f 100644 --- a/modules/example/src/smithy4s/example/OrderNumber.scala +++ b/modules/example/src/smithy4s/example/OrderNumber.scala @@ -7,5 +7,5 @@ object OrderNumber extends Newtype[Int] { val id: smithy4s.ShapeId = smithy4s.ShapeId("smithy4s.example", "OrderNumber") val hints : smithy4s.Hints = smithy4s.Hints.empty val underlyingSchema : smithy4s.Schema[Int] = int.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[OrderNumber] = bijection(underlyingSchema, OrderNumber(_), (_ : OrderNumber).value) + implicit val schema : smithy4s.Schema[OrderNumber] = bijection(underlyingSchema, OrderNumber.make, (_ : OrderNumber).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/SomeValue.scala b/modules/example/src/smithy4s/example/SomeValue.scala index 206bbbb83..c83d4591d 100644 --- a/modules/example/src/smithy4s/example/SomeValue.scala +++ b/modules/example/src/smithy4s/example/SomeValue.scala @@ -7,5 +7,5 @@ object SomeValue extends Newtype[String] { val id: smithy4s.ShapeId = smithy4s.ShapeId("smithy4s.example", "SomeValue") val hints : smithy4s.Hints = smithy4s.Hints.empty val underlyingSchema : smithy4s.Schema[String] = string.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[SomeValue] = bijection(underlyingSchema, SomeValue(_), (_ : SomeValue).value) + implicit val schema : smithy4s.Schema[SomeValue] = bijection(underlyingSchema, SomeValue.make, (_ : SomeValue).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/StreamedBlob.scala b/modules/example/src/smithy4s/example/StreamedBlob.scala index b5c923d76..1763ffbc9 100644 --- a/modules/example/src/smithy4s/example/StreamedBlob.scala +++ b/modules/example/src/smithy4s/example/StreamedBlob.scala @@ -9,5 +9,5 @@ object StreamedBlob extends Newtype[Byte] { smithy.api.Streaming(), ) val underlyingSchema : smithy4s.Schema[Byte] = byte.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[StreamedBlob] = bijection(underlyingSchema, StreamedBlob(_), (_ : StreamedBlob).value) + implicit val schema : smithy4s.Schema[StreamedBlob] = bijection(underlyingSchema, StreamedBlob.make, (_ : StreamedBlob).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/TestString.scala b/modules/example/src/smithy4s/example/TestString.scala index d7f290106..33b285581 100644 --- a/modules/example/src/smithy4s/example/TestString.scala +++ b/modules/example/src/smithy4s/example/TestString.scala @@ -9,5 +9,5 @@ object TestString extends Newtype[String] { smithy4s.example.TestTrait(Some(smithy4s.example.OrderType.InStoreOrder(smithy4s.example.OrderNumber(100), Some("someLocation")))), ) val underlyingSchema : smithy4s.Schema[String] = string.withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[TestString] = bijection(underlyingSchema, TestString(_), (_ : TestString).value) + implicit val schema : smithy4s.Schema[TestString] = bijection(underlyingSchema, TestString.make, (_ : TestString).value) } \ No newline at end of file diff --git a/modules/example/src/smithy4s/example/common/BrandList.scala b/modules/example/src/smithy4s/example/common/BrandList.scala index 0db8955d9..6fb97faab 100644 --- a/modules/example/src/smithy4s/example/common/BrandList.scala +++ b/modules/example/src/smithy4s/example/common/BrandList.scala @@ -7,5 +7,5 @@ object BrandList extends Newtype[List[String]] { val id: smithy4s.ShapeId = smithy4s.ShapeId("smithy4s.example.common", "BrandList") val hints : smithy4s.Hints = smithy4s.Hints.empty val underlyingSchema : smithy4s.Schema[List[String]] = list(string).withId(id).addHints(hints) - implicit val schema : smithy4s.Schema[BrandList] = bijection(underlyingSchema, BrandList(_), (_ : BrandList).value) + implicit val schema : smithy4s.Schema[BrandList] = bijection(underlyingSchema, BrandList.make, (_ : BrandList).value) } \ No newline at end of file From e2d5e2e25e838715223b764858933e91f3349109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20M=C3=A9lois?= Date: Thu, 7 Jul 2022 09:24:11 +0200 Subject: [PATCH 13/13] Make Newtype.Make package private --- modules/core/src-2/Newtype.scala | 6 ++++-- modules/core/src-3/Newtype.scala | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/core/src-2/Newtype.scala b/modules/core/src-2/Newtype.scala index 9ef117373..c8dba4197 100644 --- a/modules/core/src-2/Newtype.scala +++ b/modules/core/src-2/Newtype.scala @@ -24,7 +24,9 @@ abstract class Newtype[A] extends HasId { self => type Type <: Base with _Tag @inline final def apply(a: A): Type = a.asInstanceOf[Type] - final val make: Newtype.Make[A, Type] = apply(_: A) + final val make: A => Type = new Newtype.Make[A, Type] { + def apply(a: A) = self.apply(a) + } @inline final def value(x: Type): A = x.asInstanceOf[A] @@ -54,5 +56,5 @@ abstract class Newtype[A] extends HasId { self => } object Newtype { - trait Make[A, B] extends Function[A, B] + private[smithy4s] trait Make[A, B] extends Function[A, B] } diff --git a/modules/core/src-3/Newtype.scala b/modules/core/src-3/Newtype.scala index b6f051c38..d4d092c2c 100644 --- a/modules/core/src-3/Newtype.scala +++ b/modules/core/src-3/Newtype.scala @@ -20,7 +20,9 @@ abstract class Newtype[A] extends HasId { self => opaque type Type = A def apply(a: A): Type = a - final val make: Newtype.Make[A, Type] = apply(_: A) + final val make: A => Type = new Newtype.Make[A, Type] { + def apply(a: A) = self.apply(a) + } extension (orig: Type) def value: A = orig @@ -45,5 +47,5 @@ abstract class Newtype[A] extends HasId { self => } object Newtype { - trait Make[A, B] extends Function[A, B] + private[smithy4s] trait Make[A, B] extends Function[A, B] }