diff --git a/modules/bootstrapped/test/src/smithy4s/http/HttpRequestSpec.scala b/modules/bootstrapped/test/src/smithy4s/http/HttpRequestSpec.scala index 0e64c803e..0ad7c5366 100644 --- a/modules/bootstrapped/test/src/smithy4s/http/HttpRequestSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/http/HttpRequestSpec.scala @@ -38,7 +38,7 @@ final class HttpRequestSpec extends FunSuite { val resultUri = writer.write(request, Foo("hello")).uri assertEquals( resultUri, - uri.copy(host = Some("test.hello-other.example.com")) + uri.withHost(Some("test.hello-other.example.com")) ) } diff --git a/modules/bootstrapped/test/src/smithy4s/http/MatchPathSpec.scala b/modules/bootstrapped/test/src/smithy4s/http/MatchPathSpec.scala index e45811e9d..aff04f3d8 100644 --- a/modules/bootstrapped/test/src/smithy4s/http/MatchPathSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/http/MatchPathSpec.scala @@ -53,10 +53,9 @@ class MatchPathSpec() extends munit.FunSuite with munit.ScalaCheckSuite { implicit val showPathSegment: Show[PathSegment] = Show.fromToString private val renderExampleSegment: PathSegment => String = { - case LabelSegment(_) => "label-example" - case StaticSegment(value) => value - case GreedySegment(_) => - "greedy/example" + case _: LabelSegment => "label-example" + case ss: StaticSegment => ss.value + case _: GreedySegment => "greedy/example" } property("Doesn't throw on empty path") { diff --git a/modules/bootstrapped/test/src/smithy4s/schema/SchemaPartitionSpec.scala b/modules/bootstrapped/test/src/smithy4s/schema/SchemaPartitionSpec.scala index 95f7046ba..586730e3d 100644 --- a/modules/bootstrapped/test/src/smithy4s/schema/SchemaPartitionSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/schema/SchemaPartitionSpec.scala @@ -77,9 +77,9 @@ final class SchemaPartitionSpec extends FunSuite { import SchemaPartition._ xPartialSchema match { - case SplittingMatch(xSchema, ySchema) => - val decoderX = Document.Decoder.fromSchema(xSchema) - val decoderY = Document.Decoder.fromSchema(ySchema) + case sm: SplittingMatch[_] => + val decoderX = Document.Decoder.fromSchema(sm.matching) + val decoderY = Document.Decoder.fromSchema(sm.notMatching) val result = (decoderX.decode(documentX), decoderY.decode(documentY)).mapN { @@ -111,9 +111,9 @@ final class SchemaPartitionSpec extends FunSuite { import SchemaPartition._ xPartialSchema match { - case (SplittingMatch(xSchema, ySchema)) => - val encoderX = Document.Encoder.fromSchema(xSchema) - val encoderY = Document.Encoder.fromSchema(ySchema) + case sm: SplittingMatch[_] => + val encoderX = Document.Encoder.fromSchema(sm.matching) + val encoderY = Document.Encoder.fromSchema(sm.notMatching) val input = PartialData.Total(Foo(1, 2)) assertEquals(encoderX.encode(input), documentX) @@ -136,9 +136,9 @@ final class SchemaPartitionSpec extends FunSuite { import SchemaPartition._ (xPartialSchema) match { - case (SplittingMatch(xSchema, ySchema)) => - val decoderX = Document.Decoder.fromSchema(xSchema) - val decoderY = Document.Decoder.fromSchema(ySchema) + case sm: SplittingMatch[_] => + val decoderX = Document.Decoder.fromSchema(sm.matching) + val decoderY = Document.Decoder.fromSchema(sm.notMatching) val result = (decoderX.decode(documentX), decoderY.decode(documentY)).mapN { @@ -173,9 +173,9 @@ final class SchemaPartitionSpec extends FunSuite { import SchemaPartition._ xPartialSchema match { - case SplittingMatch(xSchema, ySchema) => - val decoderX = Document.Decoder.fromSchema(xSchema) - val decoderY = Document.Decoder.fromSchema(ySchema) + case sm: SplittingMatch[_] => + val decoderX = Document.Decoder.fromSchema(sm.matching) + val decoderY = Document.Decoder.fromSchema(sm.notMatching) val result = (decoderX.decode(documentX), decoderY.decode(documentY)).mapN { @@ -202,9 +202,9 @@ final class SchemaPartitionSpec extends FunSuite { import SchemaPartition._ xPartialSchema match { - case (TotalMatch(xSchema)) => + case tm: TotalMatch[_] => val originalDecoder = Document.Decoder.fromSchema(schema) - val payloadDecoder = Document.Decoder.fromSchema(xSchema) + val payloadDecoder = Document.Decoder.fromSchema(tm.schema) val orignalResult = originalDecoder.decode(Document.obj("x" -> documentX)) @@ -233,8 +233,8 @@ final class SchemaPartitionSpec extends FunSuite { ): List[Document] => Either[codecs.PayloadError, A] = { val allDecoders: List[Document.Decoder[PartialData[A]]] = predicates.toList.map(schema.partition(_)).collect { - case SchemaPartition.SplittingMatch(matching, _) => - Document.Decoder.fromSchema(matching) + case sm: SchemaPartition.SplittingMatch[_] => + Document.Decoder.fromSchema(sm.matching) } (documents: List[Document]) => allDecoders diff --git a/modules/core/src-jvm-native/smithy4s/Timestamp.scala b/modules/core/src-jvm-native/smithy4s/Timestamp.scala index f21072b15..210d27f05 100644 --- a/modules/core/src-jvm-native/smithy4s/Timestamp.scala +++ b/modules/core/src-jvm-native/smithy4s/Timestamp.scala @@ -23,6 +23,13 @@ import scala.util.control.NonFatal case class Timestamp private (epochSecond: Long, nano: Int) extends TimestampPlatform { + def withEpochSecond(value: Long): Timestamp = { + copy(epochSecond = value) + } + + def withNano(value: Int): Timestamp = { + copy(nano = value) + } def isAfter(other: Timestamp): Boolean = { val diff = epochSecond - other.epochSecond diff > 0 || diff == 0 && nano > other.nano @@ -171,7 +178,10 @@ case class Timestamp private (epochSecond: Long, nano: Int) } object Timestamp extends TimestampCompanionPlatform { - + @scala.annotation.nowarn( + "msg=private method unapply in object Timestamp is never used" + ) + private def unapply(c: Timestamp): Option[Timestamp] = Some(c) val epoch = Timestamp(0, 0) private val digits: Array[Short] = Array( diff --git a/modules/core/src/smithy4s/ConstraintError.scala b/modules/core/src/smithy4s/ConstraintError.scala index af0b5c7a5..8c08c6fb8 100644 --- a/modules/core/src/smithy4s/ConstraintError.scala +++ b/modules/core/src/smithy4s/ConstraintError.scala @@ -16,8 +16,25 @@ package smithy4s -final case class ConstraintError(hint: Hint, message: String) +final case class ConstraintError private (hint: Hint, message: String) extends Throwable with scala.util.control.NoStackTrace { + def withHint(value: Hint): ConstraintError = { + copy(hint = value) + } + + def withMessage(value: String): ConstraintError = { + copy(message = value) + } override def getMessage() = s"$hint: $message" } + +object ConstraintError { + @scala.annotation.nowarn( + "msg=private method unapply in object ConstraintError is never used" + ) + private def unapply(c: ConstraintError): Option[ConstraintError] = Some(c) + def apply(hint: Hint, message: String): ConstraintError = { + new ConstraintError(hint, message) + } +} diff --git a/modules/core/src/smithy4s/Hints.scala b/modules/core/src/smithy4s/Hints.scala index c4d35ba38..716480e74 100644 --- a/modules/core/src/smithy4s/Hints.scala +++ b/modules/core/src/smithy4s/Hints.scala @@ -131,8 +131,8 @@ object Hints { private def mapFromSeq(bindings: Seq[Hint]): Map[ShapeId, Hint] = { bindings.map { - case b @ Binding.StaticBinding(k, _) => k.id -> b - case b @ Binding.DynamicBinding(k, _) => k -> b + case b: Binding.StaticBinding[_] => b.key.id -> b + case b: Binding.DynamicBinding => b.keyId -> b }.toMap } @@ -145,10 +145,10 @@ object Hints { def all: Iterable[Hint] = toMap.values def get[A](implicit key: ShapeTag[A]): Option[A] = toMap.get(key.id).flatMap { - case Binding.StaticBinding(k, value) => - if (key.eq(k)) Some(value.asInstanceOf[A]) else None - case Binding.DynamicBinding(_, value) => - Document.Decoder.fromSchema(key.schema).decode(value).toOption + case sb: Binding.StaticBinding[_] => + if (key.eq(sb.key)) Some(sb.value.asInstanceOf[A]) else None + case db: Binding.DynamicBinding => + Document.Decoder.fromSchema(key.schema).decode(db.value).toOption } def ++(other: Hints): Hints = concat(this, other) @@ -229,15 +229,51 @@ object Hints { } object Binding { - final case class StaticBinding[A](key: ShapeTag[A], value: A) + final case class StaticBinding[A] private (key: ShapeTag[A], value: A) extends Binding { + def withKey(value: ShapeTag[A]): StaticBinding[A] = { + copy(key = value) + } + + def withValue(value: A): StaticBinding[A] = { + copy(value = value) + } override def keyId: ShapeId = key.id override def toString: String = value.toString() } - final case class DynamicBinding(keyId: ShapeId, value: Document) + object StaticBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object StaticBinding is never used" + ) + private def unapply[A](c: StaticBinding[A]): Option[StaticBinding[A]] = + Some( + c + ) + def apply[A](key: ShapeTag[A], value: A): StaticBinding[A] = { + new StaticBinding(key, value) + } + } + + final case class DynamicBinding private (keyId: ShapeId, value: Document) extends Binding { + def withKeyId(value: ShapeId): DynamicBinding = { + copy(keyId = value) + } + + def withValue(value: Document): DynamicBinding = { + copy(value = value) + } override def toString = Document.obj(keyId.show -> value).toString() } + object DynamicBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object DynamicBinding is never used" + ) + private def unapply(c: DynamicBinding): Option[DynamicBinding] = Some(c) + def apply(keyId: ShapeId, value: Document): DynamicBinding = { + new DynamicBinding(keyId, value) + } + } implicit def fromValue[A, AA <: A](value: AA)(implicit key: ShapeTag[A] diff --git a/modules/core/src/smithy4s/PartialData.scala b/modules/core/src/smithy4s/PartialData.scala index 1ff51cc0c..f59c7d162 100644 --- a/modules/core/src/smithy4s/PartialData.scala +++ b/modules/core/src/smithy4s/PartialData.scala @@ -50,14 +50,46 @@ import scala.collection.compat.immutable.ArraySeq sealed trait PartialData[A] { def map[B](f: A => B): PartialData[B] } -// format: off + object PartialData { - final case class Total[A](a: A) extends PartialData[A] { + final case class Total[A] private (a: A) extends PartialData[A] { + def withA(value: A): Total[A] = { + copy(a = value) + } def map[B](f: A => B): PartialData[B] = Total(f(a)) } - final case class Partial[A](indexes: IndexedSeq[Int], partialData: IndexedSeq[Any], make: IndexedSeq[Any] => A) extends PartialData[A] { + object Total { + @scala.annotation.nowarn( + "msg=private method unapply in object Total is never used" + ) + private def unapply[A](c: Total[A]): Option[Total[A]] = Some(c) + def apply[A](a: A): Total[A] = { + new Total(a) + } + } + + // scalafmt: {maxColumn: 160} + final case class Partial[A] private (indexes: IndexedSeq[Int], partialData: IndexedSeq[Any], make: IndexedSeq[Any] => A) extends PartialData[A] { + def withIndexes(value: IndexedSeq[Int]): Partial[A] = { + copy(indexes = value) + } + + def withPartialData(value: IndexedSeq[Any]): Partial[A] = { + copy(partialData = value) + } + + def withMake(value: IndexedSeq[Any] => A): Partial[A] = { + copy(make = value) + } def map[B](f: A => B): PartialData[B] = Partial(indexes, partialData, make andThen f) } + object Partial { + @scala.annotation.nowarn("msg=private method unapply in object Partial is never used") + private def unapply[A](c: Partial[A]): Option[Partial[A]] = Some(c) + def apply[A](indexes: IndexedSeq[Int], partialData: IndexedSeq[Any], make: IndexedSeq[Any] => A): Partial[A] = { + new Partial(indexes, partialData, make) + } + } /** * Reconciles bits of partial data (typically retrieved from various parts of a message) @@ -65,25 +97,25 @@ object PartialData { * the individual pieces can be reconciled into the full data. */ def unsafeReconcile[A](pieces: PartialData[A]*): A = { - pieces.collectFirst { - case Total(a) => a - }.getOrElse { - val allPieces = pieces.asInstanceOf[Seq[PartialData.Partial[A]]] - var totalSize = 0 - allPieces.foreach(totalSize += _.indexes.size) - val array = Array.fill[Any](totalSize)(null) - var make : IndexedSeq[Any] => A = null - allPieces.foreach { case PartialData.Partial(indexes, data, const) => - // all the `const` values should be the same, therefore which one is called - // is an arbitrary choice. - make = const - var i = 0 - while(i < data.size) { - array(indexes(i)) = data(i) - i += 1 + pieces + .collectFirst { case t: Total[_] => t.a } + .getOrElse { + val allPieces = pieces.asInstanceOf[Seq[PartialData.Partial[A]]] + var totalSize = 0 + allPieces.foreach(totalSize += _.indexes.size) + val array = Array.fill[Any](totalSize)(null) + var make: IndexedSeq[Any] => A = null + allPieces.foreach { case p: PartialData.Partial[_] => + // all the `const` values should be the same, therefore which one is called + // is an arbitrary choice. + make = p.make + var i = 0 + while (i < p.partialData.size) { + array(p.indexes(i)) = p.partialData(i) + i += 1 + } } + make(ArraySeq.unsafeWrapArray(array)) } - make(ArraySeq.unsafeWrapArray(array)) - } } } diff --git a/modules/core/src/smithy4s/Service.scala b/modules/core/src/smithy4s/Service.scala index 63907324e..ac1fc17fc 100644 --- a/modules/core/src/smithy4s/Service.scala +++ b/modules/core/src/smithy4s/Service.scala @@ -201,6 +201,9 @@ object Service { } object Builder { + @scala.annotation.nowarn("msg=private method unapply in object Builder is never used") + private def unapply[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](c: Builder[Alg, Op]): Option[Builder[Alg, Op]] = Some(c) + def fromService[Alg[_[_, _, _, _, _]]]( service: Service[Alg] ): Builder[Alg, service.Operation] = @@ -215,6 +218,26 @@ object Service { private val baseHints: Hints, ) { + def withBase(value: Service.Aux[Alg, Op]): Builder[Alg, Op] = { + copy(base = value) + } + + def withBaseEndpoints(value: IndexedSeq[Endpoint[Op, _, _, _, _, _]]): Builder[Alg, Op] = { + copy(baseEndpoints = value) + } + + def withBaseId(value: ShapeId): Builder[Alg, Op] = { + copy(baseId = value) + } + + def withBaseVersion(value: String): Builder[Alg, Op] = { + copy(baseVersion = value) + } + + def withBaseHints(value: Hints): Builder[Alg, Op] = { + copy(baseHints = value) + } + def mapEndpointEach( mapper: PolyFunction5[Endpoint.ForOperation[Op]#e, Endpoint.ForOperation[Op]#e] ): Builder[Alg, Op] = { diff --git a/modules/core/src/smithy4s/ShapeId.scala b/modules/core/src/smithy4s/ShapeId.scala index 48801e5d5..11486db12 100644 --- a/modules/core/src/smithy4s/ShapeId.scala +++ b/modules/core/src/smithy4s/ShapeId.scala @@ -18,7 +18,15 @@ package smithy4s import smithy.api.IdRef -final case class ShapeId(namespace: String, name: String) extends HasId { +final case class ShapeId private (namespace: String, name: String) + extends HasId { + def withNamespace(value: String): ShapeId = { + copy(namespace = value) + } + + def withName(value: String): ShapeId = { + copy(name = value) + } def show = s"$namespace#$name" def withMember(member: String): ShapeId.Member = ShapeId.Member(this, member) override def toString = show @@ -26,6 +34,14 @@ final case class ShapeId(namespace: String, name: String) extends HasId { } object ShapeId extends ShapeTag.Has[ShapeId] { self => + @scala.annotation.nowarn( + "msg=private method unapply in object ShapeId is never used" + ) + private def unapply(c: ShapeId): Option[ShapeId] = Some(c) + def apply(namespace: String, name: String): ShapeId = { + new ShapeId(namespace, name) + } + def parse(string: String): Option[ShapeId] = { if (!string.contains('#')) None else { @@ -40,7 +56,25 @@ object ShapeId extends ShapeTag.Has[ShapeId] { self => } } - final case class Member(shapeId: ShapeId, member: String) + final case class Member private (shapeId: ShapeId, member: String) { + def withShapeId(value: ShapeId): Member = { + copy(shapeId = value) + } + + def withMember(value: String): Member = { + copy(member = value) + } + + } + object Member { + @scala.annotation.nowarn( + "msg=private method unapply in object Member is never used" + ) + private def unapply(c: Member): Option[Member] = Some(c) + def apply(shapeId: ShapeId, member: String): Member = { + new Member(shapeId, member) + } + } val id: ShapeId = ShapeId("smithy4s", "ShapeId") lazy val schema = diff --git a/modules/core/src/smithy4s/UnsupportedProtocolError.scala b/modules/core/src/smithy4s/UnsupportedProtocolError.scala index 9ea351219..bff1e5bdd 100644 --- a/modules/core/src/smithy4s/UnsupportedProtocolError.scala +++ b/modules/core/src/smithy4s/UnsupportedProtocolError.scala @@ -16,8 +16,29 @@ package smithy4s -final case class UnsupportedProtocolError(service: HasId, protocolTag: HasId) - extends Throwable { +final case class UnsupportedProtocolError private ( + service: HasId, + protocolTag: HasId +) extends Throwable { + def withService(value: HasId): UnsupportedProtocolError = { + copy(service = value) + } + + def withProtocolTag(value: HasId): UnsupportedProtocolError = { + copy(protocolTag = value) + } override def getMessage(): String = s"Service ${service.id.show} does not support the ${protocolTag.id.show} protocol" } + +object UnsupportedProtocolError { + @scala.annotation.nowarn( + "msg=private method unapply in object UnsupportedProtocolError is never used" + ) + private def unapply( + c: UnsupportedProtocolError + ): Option[UnsupportedProtocolError] = Some(c) + def apply(service: HasId, protocolTag: HasId): UnsupportedProtocolError = { + new UnsupportedProtocolError(service, protocolTag) + } +} diff --git a/modules/core/src/smithy4s/codecs/PayloadError.scala b/modules/core/src/smithy4s/codecs/PayloadError.scala index 063bc4d3d..781179c6e 100644 --- a/modules/core/src/smithy4s/codecs/PayloadError.scala +++ b/modules/core/src/smithy4s/codecs/PayloadError.scala @@ -19,18 +19,41 @@ package smithy4s.codecs import smithy4s.schema.Schema._ import smithy4s.schema._ -case class PayloadError( +case class PayloadError private ( path: PayloadPath, expected: String, message: String ) extends Throwable with scala.util.control.NoStackTrace { + def withPath(value: PayloadPath): PayloadError = { + copy(path = value) + } + + def withExpected(value: String): PayloadError = { + copy(expected = value) + } + + def withMessage(value: String): PayloadError = { + copy(message = value) + } override def toString(): String = s"PayloadError($path, expected = $expected, message=$message)" override def getMessage(): String = s"$message (path: $path)" } object PayloadError { + @scala.annotation.nowarn( + "msg=private method unapply in object PayloadError is never used" + ) + private def unapply(c: PayloadError): Option[PayloadError] = Some(c) + def apply( + path: PayloadPath, + expected: String, + message: String + ): PayloadError = { + new PayloadError(path, expected, message) + } + val schema: Schema[PayloadError] = { val path = PayloadPath.schema.required[PayloadError]("path", _.path) val expected = string.required[PayloadError]("expected", _.expected) diff --git a/modules/core/src/smithy4s/codecs/PayloadPath.scala b/modules/core/src/smithy4s/codecs/PayloadPath.scala index 973982d8e..671e2cec0 100644 --- a/modules/core/src/smithy4s/codecs/PayloadPath.scala +++ b/modules/core/src/smithy4s/codecs/PayloadPath.scala @@ -18,8 +18,11 @@ package smithy4s.codecs import smithy4s.schema._ -case class PayloadPath(segments: List[PayloadPath.Segment]) { +case class PayloadPath private (segments: List[PayloadPath.Segment]) { + def withSegments(value: List[PayloadPath.Segment]): PayloadPath = { + copy(segments = value) + } def append(segment: PayloadPath.Segment): PayloadPath = copy(segments ::: List(segment)) @@ -35,12 +38,19 @@ case class PayloadPath(segments: List[PayloadPath.Segment]) { object PayloadPath { + @scala.annotation.nowarn( + "msg=private method unapply in object PayloadPath is never used" + ) + private def unapply(c: PayloadPath): Option[PayloadPath] = Some(c) val root = PayloadPath(segments = List.empty) - def apply(segments: PayloadPath.Segment*): PayloadPath = PayloadPath( + def apply(segments: PayloadPath.Segment*): PayloadPath = new PayloadPath( segments.toList ) + def fromSegments(segments: List[PayloadPath.Segment]): PayloadPath = + new PayloadPath(segments) + def parse(string: String): PayloadPath = PayloadPath( string.split('.').filter(_.nonEmpty).map(Segment.parse).toList ) @@ -68,13 +78,39 @@ object PayloadPath { case _: Throwable => Label(string) } - case class Label(label: String) extends Segment { + case class Label private (label: String) extends Segment { + def withLabel(value: String): Label = { + copy(label = value) + } override lazy val render: String = label } - case class Index(index: Int) extends Segment { + + object Label { + @scala.annotation.nowarn( + "msg=private method unapply in object Label is never used" + ) + private def unapply(c: Label): Option[Label] = Some(c) + def apply(label: String): Label = { + new Label(label) + } + } + case class Index private (index: Int) extends Segment { + def withIndex(value: Int): Index = { + copy(index = value) + } override lazy val render: String = index.toString } + object Index { + @scala.annotation.nowarn( + "msg=private method unapply in object Index is never used" + ) + private def unapply(c: Index): Option[Index] = Some(c) + def apply(index: Int): Index = { + new Index(index) + } + } + implicit def stringConversion(label: String): Segment = Label(label) implicit def intConversion(index: Int): Segment = Index(index) } diff --git a/modules/core/src/smithy4s/codecs/StringAndBlobCodecs.scala b/modules/core/src/smithy4s/codecs/StringAndBlobCodecs.scala index 089a2f516..d6da0c4b6 100644 --- a/modules/core/src/smithy4s/codecs/StringAndBlobCodecs.scala +++ b/modules/core/src/smithy4s/codecs/StringAndBlobCodecs.scala @@ -84,14 +84,14 @@ object StringAndBlobCodecs { } } }) - case EnumTag.OpenStringEnum(processUnknown) => + case ose: EnumTag.OpenStringEnum[_] => Some(new BlobDecoder[E] { def decode(blob: Blob): Either[PayloadError, E] = { val str = blob.toUTF8String val result: E = values .find(_.stringValue == str) .map(_.value) - .getOrElse(processUnknown(str)) + .getOrElse(ose.unknown(str)) Right(result) } }) @@ -162,7 +162,7 @@ object StringAndBlobCodecs { tag match { case EnumTag.ClosedStringEnum => Some(stringEncoder.contramap(total(_: E).stringValue)) - case EnumTag.OpenStringEnum(_) => + case _: EnumTag.OpenStringEnum[_] => Some(stringEncoder.contramap(total(_: E).stringValue)) case _ => None } diff --git a/modules/core/src/smithy4s/http/HttpBinding.scala b/modules/core/src/smithy4s/http/HttpBinding.scala index 3ec2130cb..3b8f665d5 100644 --- a/modules/core/src/smithy4s/http/HttpBinding.scala +++ b/modules/core/src/smithy4s/http/HttpBinding.scala @@ -32,12 +32,12 @@ sealed abstract class HttpBinding(val tpe: HttpBinding.Type) with Serializable { def show: String = this match { - case HeaderBinding(httpName) => s"Header $httpName" - case HeaderPrefixBinding(prefix) => s"Headers prefixed by $prefix" - case QueryBinding(httpName) => s"Query parameter $httpName" - case QueryParamsBinding => "Query parameters" - case PathBinding(httpName) => s"Path parameter $httpName" - case StatusCodeBinding => "Status code" + case hb: HeaderBinding => s"Header ${hb.httpName}" + case hpb: HeaderPrefixBinding => s"Headers prefixed by ${hpb.prefix}" + case qb: QueryBinding => s"Query parameter ${qb.httpName}" + case QueryParamsBinding => "Query parameters" + case pb: PathBinding => s"Path parameter ${pb.httpName}" + case StatusCodeBinding => "Status code" } } @@ -54,38 +54,93 @@ object HttpBinding extends ShapeTag.Companion[HttpBinding] { case object StatusCodeType extends Type } - case class HeaderBinding(httpName: CaseInsensitive) - extends HttpBinding(Type.HeaderType) - case class HeaderPrefixBinding(prefix: String) - extends HttpBinding(Type.HeaderType) - case class QueryBinding(httpName: String) extends HttpBinding(Type.QueryType) + case class HeaderBinding private (httpName: CaseInsensitive) + extends HttpBinding(Type.HeaderType) { + def withHttpName(value: CaseInsensitive): HeaderBinding = { + copy(httpName = value) + } + + } + case class HeaderPrefixBinding private (prefix: String) + extends HttpBinding(Type.HeaderType) { + def withPrefix(value: String): HeaderPrefixBinding = { + copy(prefix = value) + } + + } + case class QueryBinding private (httpName: String) + extends HttpBinding(Type.QueryType) { + def withHttpName(value: String): QueryBinding = { + copy(httpName = value) + } + + } case object QueryParamsBinding extends HttpBinding(Type.QueryType) { val schema: Schema[QueryParamsBinding.type] = constant(QueryParamsBinding) } - case class PathBinding(httpName: String) extends HttpBinding(Type.PathType) + case class PathBinding private (httpName: String) + extends HttpBinding(Type.PathType) { + def withHttpName(value: String): PathBinding = { + copy(httpName = value) + } + + } case object StatusCodeBinding extends HttpBinding(Type.StatusCodeType) { val schema: Schema[StatusCodeBinding.type] = constant(StatusCodeBinding) } object HeaderBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object HeaderBinding is never used" + ) + private def unapply(c: HeaderBinding): Option[HeaderBinding] = Some(c) + def apply(httpName: CaseInsensitive): HeaderBinding = { + new HeaderBinding(httpName) + } + val schema: Schema[HeaderBinding] = struct(string.required[HeaderBinding]("httpName", _.httpName.toString))( string => HeaderBinding(CaseInsensitive(string)) ) } object HeaderPrefixBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object HeaderPrefixBinding is never used" + ) + private def unapply(c: HeaderPrefixBinding): Option[HeaderPrefixBinding] = + Some(c) + def apply(prefix: String): HeaderPrefixBinding = { + new HeaderPrefixBinding(prefix) + } + val schema: Schema[HeaderPrefixBinding] = struct(string.required[HeaderPrefixBinding]("prefix", _.prefix))( HeaderPrefixBinding.apply ) } object QueryBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object QueryBinding is never used" + ) + private def unapply(c: QueryBinding): Option[QueryBinding] = Some(c) + def apply(httpName: String): QueryBinding = { + new QueryBinding(httpName) + } + val schema: Schema[QueryBinding] = struct(string.required[QueryBinding]("httpName", _.httpName))( QueryBinding.apply ) } object PathBinding { + @scala.annotation.nowarn( + "msg=private method unapply in object PathBinding is never used" + ) + private def unapply(c: PathBinding): Option[PathBinding] = Some(c) + def apply(httpName: String): PathBinding = { + new PathBinding(httpName) + } + val schema: Schema[PathBinding] = struct(string.required[PathBinding]("httpName", _.httpName))( PathBinding.apply diff --git a/modules/core/src/smithy4s/http/HttpContractError.scala b/modules/core/src/smithy4s/http/HttpContractError.scala index 2b381a507..a59abf85b 100644 --- a/modules/core/src/smithy4s/http/HttpContractError.scala +++ b/modules/core/src/smithy4s/http/HttpContractError.scala @@ -51,17 +51,40 @@ object HttpContractError { } -case class HttpPayloadError( +case class HttpPayloadError private ( path: PayloadPath, expected: String, message: String ) extends HttpContractError { + def withPath(value: PayloadPath): HttpPayloadError = { + copy(path = value) + } + + def withExpected(value: String): HttpPayloadError = { + copy(expected = value) + } + + def withMessage(value: String): HttpPayloadError = { + copy(message = value) + } override def toString(): String = s"HttpPayloadError($path, expected = $expected, message=$message)" override def getMessage(): String = s"$message (path: $path)" } object HttpPayloadError { + @scala.annotation.nowarn( + "msg=private method unapply in object HttpPayloadError is never used" + ) + private def unapply(c: HttpPayloadError): Option[HttpPayloadError] = Some(c) + def apply( + path: PayloadPath, + expected: String, + message: String + ): HttpPayloadError = { + new HttpPayloadError(path, expected, message) + } + val schema: Schema[HttpPayloadError] = { val path = PayloadPath.schema.required[HttpPayloadError]("path", _.path) val expected = string.required[HttpPayloadError]("expected", _.expected) @@ -74,39 +97,86 @@ sealed trait MetadataError extends HttpContractError { import MetadataError._ override def getMessage(): String = this match { - case NotFound(field, location) => - s"${location.show} was not found (field $field)" - case WrongType(field, location, expectedType, value) => - s"""String "$value", found in ${location.show}, does not fit field $field ($expectedType)""" - case ArityError(field, location) => - s"Field $field expects a single value to be found at ${location.show}" - case FailedConstraint(field, location, message) => - s"Field $field, found in ${location.show}, failed constraint checks with message: $message" - case ImpossibleDecoding(message) => - message + case nf: NotFound => + s"${nf.location.show} was not found (field ${nf.field})" + case wt: WrongType => + s"""String "${wt.value}", found in ${wt.location.show}, does not fit field ${wt.field} (${wt.expectedType})""" + case ae: ArityError => + s"Field ${ae.field} expects a single value to be found at ${ae.location.show}" + case fc: FailedConstraint => + s"Field ${fc.field}, found in ${fc.location.show}, failed constraint checks with message: ${fc.message}" + case id: ImpossibleDecoding => + id.message } } object MetadataError { - case class NotFound(field: String, location: HttpBinding) - extends MetadataError + case class NotFound private (field: String, location: HttpBinding) + extends MetadataError { + def withField(value: String): NotFound = { + copy(field = value) + } + + def withLocation(value: HttpBinding): NotFound = { + copy(location = value) + } + + } object NotFound { + @scala.annotation.nowarn( + "msg=private method unapply in object NotFound is never used" + ) + private def unapply(c: NotFound): Option[NotFound] = Some(c) + def apply(field: String, location: HttpBinding): NotFound = { + new NotFound(field, location) + } + val schema: Schema[NotFound] = struct( string.required[NotFound]("field", _.field), HttpBinding.schema.required[NotFound]("location", _.location) )(NotFound.apply) } - case class WrongType( + case class WrongType private ( field: String, location: HttpBinding, expectedType: String, value: String - ) extends MetadataError + ) extends MetadataError { + def withField(value: String): WrongType = { + copy(field = value) + } + + def withLocation(value: HttpBinding): WrongType = { + copy(location = value) + } + + def withExpectedType(value: String): WrongType = { + copy(expectedType = value) + } + + def withValue(value: String): WrongType = { + copy(value = value) + } + + } object WrongType { + @scala.annotation.nowarn( + "msg=private method unapply in object WrongType is never used" + ) + private def unapply(c: WrongType): Option[WrongType] = Some(c) + def apply( + field: String, + location: HttpBinding, + expectedType: String, + value: String + ): WrongType = { + new WrongType(field, location, expectedType, value) + } + val schema = struct( string.required[WrongType]("field", _.field), HttpBinding.schema.required[WrongType]("location", _.location), @@ -115,25 +185,67 @@ object MetadataError { )(WrongType.apply) } - case class ArityError( + case class ArityError private ( field: String, location: HttpBinding - ) extends MetadataError + ) extends MetadataError { + def withField(value: String): ArityError = { + copy(field = value) + } + + def withLocation(value: HttpBinding): ArityError = { + copy(location = value) + } + + } object ArityError { + @scala.annotation.nowarn( + "msg=private method unapply in object ArityError is never used" + ) + private def unapply(c: ArityError): Option[ArityError] = Some(c) + def apply(field: String, location: HttpBinding): ArityError = { + new ArityError(field, location) + } + val schema = struct( string.required[ArityError]("field", _.field), HttpBinding.schema.required[ArityError]("location", _.location) )(ArityError.apply) } - case class FailedConstraint( + case class FailedConstraint private ( field: String, location: HttpBinding, message: String - ) extends MetadataError + ) extends MetadataError { + def withField(value: String): FailedConstraint = { + copy(field = value) + } + + def withLocation(value: HttpBinding): FailedConstraint = { + copy(location = value) + } + + def withMessage(value: String): FailedConstraint = { + copy(message = value) + } + + } object FailedConstraint { + @scala.annotation.nowarn( + "msg=private method unapply in object FailedConstraint is never used" + ) + private def unapply(c: FailedConstraint): Option[FailedConstraint] = Some(c) + def apply( + field: String, + location: HttpBinding, + message: String + ): FailedConstraint = { + new FailedConstraint(field, location, message) + } + val schema = struct( string.required[FailedConstraint]("field", _.field), HttpBinding.schema.required[FailedConstraint]("location", _.location), @@ -141,11 +253,24 @@ object MetadataError { )(FailedConstraint.apply) } - case class ImpossibleDecoding( + case class ImpossibleDecoding private ( message: String - ) extends MetadataError + ) extends MetadataError { + def withMessage(value: String): ImpossibleDecoding = { + copy(message = value) + } + + } object ImpossibleDecoding { + @scala.annotation.nowarn( + "msg=private method unapply in object ImpossibleDecoding is never used" + ) + private def unapply(c: ImpossibleDecoding): Option[ImpossibleDecoding] = + Some(c) + def apply(message: String): ImpossibleDecoding = { + new ImpossibleDecoding(message) + } val schema = struct( string.required[ImpossibleDecoding]("message", _.message) )(ImpossibleDecoding.apply) diff --git a/modules/core/src/smithy4s/http/HttpDiscriminator.scala b/modules/core/src/smithy4s/http/HttpDiscriminator.scala index 3fc5630ac..2131a9420 100644 --- a/modules/core/src/smithy4s/http/HttpDiscriminator.scala +++ b/modules/core/src/smithy4s/http/HttpDiscriminator.scala @@ -22,12 +22,55 @@ sealed trait HttpDiscriminator extends Product with Serializable object HttpDiscriminator { - // format: off - final case class FullId(shapeId: ShapeId) extends HttpDiscriminator - final case class NameOnly(name: String) extends HttpDiscriminator - final case class StatusCode(int: Int) extends HttpDiscriminator + final case class FullId private (shapeId: ShapeId) extends HttpDiscriminator { + def withShapeId(value: ShapeId): FullId = { + copy(shapeId = value) + } + + } + object FullId { + @scala.annotation.nowarn( + "msg=private method unapply in object FullId is never used" + ) + private def unapply(c: FullId): Option[FullId] = Some(c) + def apply(shapeId: ShapeId): FullId = { + new FullId(shapeId) + } + } + + final case class NameOnly private (name: String) extends HttpDiscriminator { + def withName(value: String): NameOnly = { + copy(name = value) + } + + } + object NameOnly { + @scala.annotation.nowarn( + "msg=private method unapply in object NameOnly is never used" + ) + private def unapply(c: NameOnly): Option[NameOnly] = Some(c) + def apply(name: String): NameOnly = { + new NameOnly(name) + } + } + + final case class StatusCode private (int: Int) extends HttpDiscriminator { + def withInt(value: Int): StatusCode = { + copy(int = value) + } + + } + object StatusCode { + @scala.annotation.nowarn( + "msg=private method unapply in object StatusCode is never used" + ) + private def unapply(c: StatusCode): Option[StatusCode] = Some(c) + def apply(int: Int): StatusCode = { + new StatusCode(int) + } + } + case object Undetermined extends HttpDiscriminator - // format: on def fromResponse( discriminatingHeaderNames: List[String], diff --git a/modules/core/src/smithy4s/http/HttpEndpoint.scala b/modules/core/src/smithy4s/http/HttpEndpoint.scala index d7c42a631..758809be7 100644 --- a/modules/core/src/smithy4s/http/HttpEndpoint.scala +++ b/modules/core/src/smithy4s/http/HttpEndpoint.scala @@ -78,6 +78,24 @@ object HttpEndpoint { } } - case class HttpEndpointError(message: String) extends Exception(message) + case class HttpEndpointError private (message: String) + extends Exception(message) { + def withMessage(value: String): HttpEndpointError = { + copy(message = value) + } + + } + + object HttpEndpointError { + @scala.annotation.nowarn( + "msg=private method unapply in object HttpEndpointError is never used" + ) + private def unapply(c: HttpEndpointError): Option[HttpEndpointError] = Some( + c + ) + def apply(message: String): HttpEndpointError = { + new HttpEndpointError(message) + } + } } diff --git a/modules/core/src/smithy4s/http/HttpErrorSelector.scala b/modules/core/src/smithy4s/http/HttpErrorSelector.scala index e07f6a12a..342930083 100644 --- a/modules/core/src/smithy4s/http/HttpErrorSelector.scala +++ b/modules/core/src/smithy4s/http/HttpErrorSelector.scala @@ -173,10 +173,10 @@ private[http] final class HttpErrorSelector[F[_]: Covariant, E]( ): Option[Alt[E, _]] = { import HttpDiscriminator._ discriminator match { - case FullId(shapeId) => byShapeId.get(shapeId) - case NameOnly(name) => byName.get(name) - case StatusCode(int) => byStatusCode(int) - case Undetermined => None + case fi: FullId => byShapeId.get(fi.shapeId) + case no: NameOnly => byName.get(no.name) + case sc: StatusCode => byStatusCode(sc.int) + case Undetermined => None } } } diff --git a/modules/core/src/smithy4s/http/HttpMethod.scala b/modules/core/src/smithy4s/http/HttpMethod.scala index 6e22cb827..ad2f35f64 100644 --- a/modules/core/src/smithy4s/http/HttpMethod.scala +++ b/modules/core/src/smithy4s/http/HttpMethod.scala @@ -18,21 +18,21 @@ package smithy4s.http sealed trait HttpMethod extends Product with Serializable { def showUppercase = this match { - case HttpMethod.PUT => "PUT" - case HttpMethod.POST => "POST" - case HttpMethod.DELETE => "DELETE" - case HttpMethod.GET => "GET" - case HttpMethod.PATCH => "PATCH" - case HttpMethod.OTHER(value) => value.toUpperCase + case HttpMethod.PUT => "PUT" + case HttpMethod.POST => "POST" + case HttpMethod.DELETE => "DELETE" + case HttpMethod.GET => "GET" + case HttpMethod.PATCH => "PATCH" + case o: HttpMethod.OTHER => o.value.toUpperCase } def showCapitalised = this match { - case HttpMethod.PUT => "Put" - case HttpMethod.POST => "Post" - case HttpMethod.DELETE => "Delete" - case HttpMethod.GET => "Get" - case HttpMethod.PATCH => "Patch" - case HttpMethod.OTHER(value) => value.capitalize + case HttpMethod.PUT => "Put" + case HttpMethod.POST => "Post" + case HttpMethod.DELETE => "Delete" + case HttpMethod.GET => "Get" + case HttpMethod.PATCH => "Patch" + case o: HttpMethod.OTHER => o.value.capitalize } } @@ -42,7 +42,22 @@ object HttpMethod { case object DELETE extends HttpMethod case object GET extends HttpMethod case object PATCH extends HttpMethod - case class OTHER(value: String) extends HttpMethod + case class OTHER private (value: String) extends HttpMethod { + def withValue(value: String): OTHER = { + copy(value = value) + } + + } + + object OTHER { + @scala.annotation.nowarn( + "msg=private method unapply in object OTHER is never used" + ) + private def unapply(c: OTHER): Option[OTHER] = Some(c) + def apply(value: String): OTHER = { + new OTHER(value) + } + } val values: List[HttpMethod] = List(PUT, POST, DELETE, GET, PATCH) diff --git a/modules/core/src/smithy4s/http/HttpRequest.scala b/modules/core/src/smithy4s/http/HttpRequest.scala index 502ccffb3..932cfdec6 100644 --- a/modules/core/src/smithy4s/http/HttpRequest.scala +++ b/modules/core/src/smithy4s/http/HttpRequest.scala @@ -22,12 +22,27 @@ import smithy4s.codecs.{Decoder => GenericDecoder} import smithy4s.kinds._ import smithy4s.schema._ -final case class HttpRequest[+A]( +final case class HttpRequest[+A] private ( method: HttpMethod, uri: HttpUri, headers: Map[CaseInsensitive, Seq[String]], body: A ) { + def withMethod(value: HttpMethod): HttpRequest[A] = { + copy(method = value) + } + + def withUri(value: HttpUri): HttpRequest[A] = { + copy(uri = value) + } + + def withHeaders(value: Map[CaseInsensitive, Seq[String]]): HttpRequest[A] = { + copy(headers = value) + } + + def withBody[A0](value: A0): HttpRequest[A0] = { + copy(body = value) + } def map[B](f: A => B): HttpRequest[B] = HttpRequest(method, uri, headers, f(body)) @@ -47,6 +62,18 @@ final case class HttpRequest[+A]( } object HttpRequest { + @scala.annotation.nowarn( + "msg=private method unapply in object HttpRequest is never used" + ) + private def unapply[A](c: HttpRequest[A]): Option[HttpRequest[A]] = Some(c) + def apply[A]( + method: HttpMethod, + uri: HttpUri, + headers: Map[CaseInsensitive, Seq[String]], + body: A + ): HttpRequest[A] = { + new HttpRequest(method, uri, headers, body) + } private[http] type Writer[Body, A] = smithy4s.codecs.Writer[HttpRequest[Body], A] private[http] type Decoder[F[_], Body, A] = @@ -82,7 +109,7 @@ object HttpRequest { val staticQueries = httpEndpoint.staticQueryParams val oldUri = request.uri val newUri = - oldUri.copy(path = oldUri.path ++ path, queryParams = staticQueries) + oldUri.withPath(oldUri.path ++ path).withQueryParams(staticQueries) val method = httpEndpoint.method request.copy(method = method, uri = newUri) } @@ -91,8 +118,7 @@ object HttpRequest { private def metadataWriter[Body]: Writer[Body, Metadata] = { (req: HttpRequest[Body], meta: Metadata) => val oldUri = req.uri - val newUri = - oldUri.copy(queryParams = oldUri.queryParams ++ meta.query) + val newUri = oldUri.withQueryParams(oldUri.queryParams ++ meta.query) req.addHeaders(meta.headers).copy(uri = newUri) } @@ -109,8 +135,8 @@ object HttpRequest { val hostPrefix = prefixEncoder.write(List.empty, input).mkString val oldUri = request.uri val prefixedHost = oldUri.host.map(host => s"$hostPrefix$host") - val newUri = oldUri.copy(host = prefixedHost) - request.copy(uri = newUri) + val newUri = oldUri.withHost(prefixedHost) + request.withUri(newUri) } } case None => diff --git a/modules/core/src/smithy4s/http/HttpResponse.scala b/modules/core/src/smithy4s/http/HttpResponse.scala index c134453f6..e5e12d07f 100644 --- a/modules/core/src/smithy4s/http/HttpResponse.scala +++ b/modules/core/src/smithy4s/http/HttpResponse.scala @@ -26,7 +26,7 @@ import smithy4s.schema.CachedSchemaCompiler import smithy4s.schema.ErrorSchema import smithy4s.schema.Schema -final case class HttpResponse[+A]( +final case class HttpResponse[+A] private ( statusCode: Int, headers: Map[CaseInsensitive, Seq[String]], body: A @@ -35,7 +35,6 @@ final case class HttpResponse[+A]( this.copy(statusCode = statusCode) def withHeaders(headers: Map[CaseInsensitive, Seq[String]]): HttpResponse[A] = this.copy(headers = headers) - def withBody[A0](body: A0): HttpResponse[A0] = this.copy(body = body) @@ -69,6 +68,18 @@ final case class HttpResponse[+A]( } object HttpResponse { + @scala.annotation.nowarn( + "msg=private method unapply in object HttpResponse is never used" + ) + private def unapply[A](c: HttpResponse[A]): Option[HttpResponse[A]] = Some(c) + + def apply[A]( + statusCode: Int, + headers: Map[CaseInsensitive, Seq[String]], + body: A + ): HttpResponse[A] = { + new HttpResponse(statusCode, headers, body) + } private[http] type Writer[Body, A] = smithy4s.codecs.Writer[HttpResponse[Body], A] private[http] type Decoder[F[_], Body, A] = diff --git a/modules/core/src/smithy4s/http/HttpRestSchema.scala b/modules/core/src/smithy4s/http/HttpRestSchema.scala index 97f671e7e..46e62fd6a 100644 --- a/modules/core/src/smithy4s/http/HttpRestSchema.scala +++ b/modules/core/src/smithy4s/http/HttpRestSchema.scala @@ -45,12 +45,74 @@ sealed trait HttpRestSchema[A] object HttpRestSchema { - // format: off - final case class OnlyMetadata[A](schema: Schema[A]) extends HttpRestSchema[A] - final case class OnlyBody[A](schema: Schema[A]) extends HttpRestSchema[A] - final case class MetadataAndBody[A](metadataSchema: Schema[PartialData[A]], bodySchema: Schema[PartialData[A]]) extends HttpRestSchema[A] - final case class Empty[A](value: A) extends HttpRestSchema[A] - // format: on + final case class OnlyMetadata[A] private (schema: Schema[A]) + extends HttpRestSchema[A] { + def withSchema(value: Schema[A]): OnlyMetadata[A] = { + copy(schema = value) + } + + } + object OnlyMetadata { + @scala.annotation.nowarn( + "msg=private method unapply in object OnlyMetadata is never used" + ) + private def unapply[A](c: OnlyMetadata[A]): Option[OnlyMetadata[A]] = Some( + c + ) + def apply[A](schema: Schema[A]): OnlyMetadata[A] = { + new OnlyMetadata(schema) + } + } + + final case class OnlyBody[A] private (schema: Schema[A]) + extends HttpRestSchema[A] { + def withSchema(value: Schema[A]): OnlyBody[A] = { + copy(schema = value) + } + + } + object OnlyBody { + @scala.annotation.nowarn( + "msg=private method unapply in object OnlyBody is never used" + ) + private def unapply[A](c: OnlyBody[A]): Option[OnlyBody[A]] = Some(c) + def apply[A](schema: Schema[A]): OnlyBody[A] = { + new OnlyBody(schema) + } + } + + // scalafmt: {maxColumn = 160} + final case class MetadataAndBody[A] private (metadataSchema: Schema[PartialData[A]], bodySchema: Schema[PartialData[A]]) extends HttpRestSchema[A] { + def withMetadataSchema(value: Schema[PartialData[A]]): MetadataAndBody[A] = { + copy(metadataSchema = value) + } + + def withBodySchema(value: Schema[PartialData[A]]): MetadataAndBody[A] = { + copy(bodySchema = value) + } + + } + object MetadataAndBody { + @scala.annotation.nowarn("msg=private method unapply in object MetadataAndBody is never used") + private def unapply[A](c: MetadataAndBody[A]): Option[MetadataAndBody[A]] = Some(c) + def apply[A](metadataSchema: Schema[PartialData[A]], bodySchema: Schema[PartialData[A]]): MetadataAndBody[A] = { + new MetadataAndBody(metadataSchema, bodySchema) + } + } + + final case class Empty[A] private (value: A) extends HttpRestSchema[A] { + def withValue(value: A): Empty[A] = { + copy(value = value) + } + + } + object Empty { + @scala.annotation.nowarn("msg=private method unapply in object Empty is never used") + private def unapply[A](c: Empty[A]): Option[Empty[A]] = Some(c) + def apply[A](value: A): Empty[A] = { + new Empty(value) + } + } def apply[A]( fullSchema: Schema[A] @@ -64,22 +126,22 @@ object HttpRestSchema { field.memberHints.has[HttpPayload] fullSchema.findPayload(isPayloadField) match { - case TotalMatch(schema) => OnlyBody(schema) - case NoMatch() => + case tm: TotalMatch[_] => OnlyBody(tm.schema) + case _: NoMatch[_] => fullSchema.partition(isMetadataField) match { - case SplittingMatch(metadataSchema, bodySchema) => - MetadataAndBody(metadataSchema, bodySchema) - case TotalMatch(schema) => - OnlyMetadata(schema) - case NoMatch() => + case sm: SplittingMatch[_] => + MetadataAndBody(sm.matching, sm.notMatching) + case tm: TotalMatch[_] => + OnlyMetadata(tm.schema) + case _: NoMatch[_] => fullSchema match { case Schema.StructSchema(_, _, fields, make) if fields.isEmpty => Empty(make(IndexedSeq.empty)) case _ => OnlyBody(fullSchema) } } - case SplittingMatch(bodySchema, metadataSchema) => - MetadataAndBody(metadataSchema, bodySchema) + case sm: SplittingMatch[_] => + MetadataAndBody(sm.notMatching, sm.matching) } } @@ -120,30 +182,30 @@ object HttpRestSchema { val emptyBodyEncoder = bodyWriters.fromSchema(emptySchema).contramap((_: A) => ()) HttpRestSchema(fullSchema) match { - case HttpRestSchema.OnlyMetadata(metadataSchema) => + case om: HttpRestSchema.OnlyMetadata[_] => // The data can be fully decoded from the metadata. val metadataEncoder = - metadataWriters.fromSchema(metadataSchema, cache._1) + metadataWriters.fromSchema(om.schema, cache._1) if (writeEmptyStructs(fullSchema)) { emptyBodyEncoder.combine(metadataEncoder) } else metadataEncoder - case HttpRestSchema.OnlyBody(bodySchema) => + case ob: HttpRestSchema.OnlyBody[_] => // The data can be fully decoded from the body - bodyWriters.fromSchema(bodySchema, cache._2) - case HttpRestSchema.MetadataAndBody(metadataSchema, bodySchema) => + bodyWriters.fromSchema(ob.schema, cache._2) + case mb: HttpRestSchema.MetadataAndBody[_] => val metadataWriter = metadataWriters - .fromSchema(metadataSchema, cache._1) + .fromSchema(mb.metadataSchema, cache._1) .contramap[A](PartialData.Total(_)) val bodyWriter = bodyWriters - .fromSchema(bodySchema, cache._2) + .fromSchema(mb.bodySchema, cache._2) .contramap[A](PartialData.Total(_)) // The order matters here, as the metadata encoder might override headers // that would be set with body encoders (if a smithy member is annotated with // `@httpHeader("Content-Type")` for instance) bodyWriter.combine(metadataWriter) - case HttpRestSchema.Empty(_) => + case _: HttpRestSchema.Empty[_] => if (writeEmptyStructs(fullSchema)) emptyBodyEncoder else Writer.noop // format: on } @@ -180,26 +242,26 @@ object HttpRestSchema { def fromSchema[A](fullSchema: Schema[A], cache: Cache) = { // writeEmptyStructs is not relevant for reading. HttpRestSchema(fullSchema) match { - case HttpRestSchema.OnlyMetadata(metadataSchema) => + case om: HttpRestSchema.OnlyMetadata[_] => // The data can be fully decoded from the metadata, // but we still decoding Unit from the body to drain the message. val metadataDecoder = - metadataDecoderCompiler.fromSchema(metadataSchema, cache._1) + metadataDecoderCompiler.fromSchema(om.schema, cache._1) val bodyDrain = Decoder.lift(drainBody) zipper.zipMap(bodyDrain, metadataDecoder) { case (_, data) => data } - case HttpRestSchema.OnlyBody(bodySchema) => + case ob: HttpRestSchema.OnlyBody[_] => // The data can be fully decoded from the body - bodyDecoderCompiler.fromSchema(bodySchema, cache._2) - case HttpRestSchema.MetadataAndBody(metadataSchema, bodySchema) => + bodyDecoderCompiler.fromSchema(ob.schema, cache._2) + case mb: HttpRestSchema.MetadataAndBody[_] => val metadataDecoder: Decoder[F, Message, PartialData[A]] = - metadataDecoderCompiler.fromSchema(metadataSchema, cache._1) + metadataDecoderCompiler.fromSchema(mb.metadataSchema, cache._1) val bodyDecoder: Decoder[F, Message, PartialData[A]] = - bodyDecoderCompiler.fromSchema(bodySchema, cache._2) + bodyDecoderCompiler.fromSchema(mb.bodySchema, cache._2) zipper.zipMap(metadataDecoder, bodyDecoder)( PartialData.unsafeReconcile(_, _) ) - case HttpRestSchema.Empty(value) => - zipper.pure(value) + case e: HttpRestSchema.Empty[_] => + zipper.pure(e.value) // format: on } } diff --git a/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala b/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala index fbd758458..b693e97e4 100644 --- a/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala +++ b/modules/core/src/smithy4s/http/HttpUnaryClientCodecs.scala @@ -115,7 +115,7 @@ object HttpUnaryClientCodecs { def withHostPrefixInjection(enabled: Boolean): Builder[F, Request, Response] = copy(hostPrefixInjection = enabled) def build(): UnaryClientCodecs.Make[F, Request, Response] = { - val setBody: HttpRequest.Writer[Blob, Blob] = Writer.lift((req, blob) => req.copy(body = blob)) + val setBody: HttpRequest.Writer[Blob, Blob] = Writer.lift((req, blob) => req.withBody(blob)) val setBodyK = smithy4s.codecs.Encoder.pipeToWriterK[HttpRequest[Blob], Blob](setBody) val mediaTypeWriters = new CachedSchemaCompiler.Uncached[HttpRequest.Writer[Blob, *]] { diff --git a/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala b/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala index 8ec911a96..bf1628479 100644 --- a/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala +++ b/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala @@ -105,7 +105,7 @@ object HttpUnaryServerCodecs { copy(responseTransformation = responseTransformation.andThen(F.flatMap(_)(f))) def build(): UnaryServerCodecs.Make[F, Request, Response] = { - val setBody: HttpResponse.Writer[Blob, Blob] = Writer.lift((res, blob) => res.copy(body = blob)) + val setBody: HttpResponse.Writer[Blob, Blob] = Writer.lift((res, blob) => res.withBody(blob)) val setBodyK = smithy4s.codecs.Encoder.pipeToWriterK[HttpResponse[Blob], Blob](setBody) val mediaTypeWriters = new CachedSchemaCompiler.Uncached[HttpResponse.Writer[Blob, *]] { diff --git a/modules/core/src/smithy4s/http/HttpUri.scala b/modules/core/src/smithy4s/http/HttpUri.scala index 0c9b947de..0ca8bb899 100644 --- a/modules/core/src/smithy4s/http/HttpUri.scala +++ b/modules/core/src/smithy4s/http/HttpUri.scala @@ -16,7 +16,7 @@ package smithy4s.http -final case class HttpUri( +final case class HttpUri private ( scheme: HttpUriScheme, host: Option[String], port: Option[Int], @@ -30,4 +30,46 @@ final case class HttpUri( * once the routing logic has come in effect. */ pathParams: Option[Map[String, String]] -) +) { + def withScheme(value: HttpUriScheme): HttpUri = { + copy(scheme = value) + } + + def withHost(value: Option[String]): HttpUri = { + copy(host = value) + } + + def withPort(value: Option[Int]): HttpUri = { + copy(port = value) + } + + def withPath(value: IndexedSeq[String]): HttpUri = { + copy(path = value) + } + + def withQueryParams(value: Map[String, Seq[String]]): HttpUri = { + copy(queryParams = value) + } + + def withPathParams(value: Option[Map[String, String]]): HttpUri = { + copy(pathParams = value) + } + +} + +object HttpUri { + @scala.annotation.nowarn( + "msg=private method unapply in object HttpUri is never used" + ) + private def unapply(c: HttpUri): Option[HttpUri] = Some(c) + def apply( + scheme: HttpUriScheme, + host: Option[String], + port: Option[Int], + path: IndexedSeq[String], + queryParams: Map[String, Seq[String]], + pathParams: Option[Map[String, String]] + ): HttpUri = { + new HttpUri(scheme, host, port, path, queryParams, pathParams) + } +} diff --git a/modules/core/src/smithy4s/http/Metadata.scala b/modules/core/src/smithy4s/http/Metadata.scala index bc68a67e0..367e420c8 100644 --- a/modules/core/src/smithy4s/http/Metadata.scala +++ b/modules/core/src/smithy4s/http/Metadata.scala @@ -36,14 +36,30 @@ import smithy4s.schema.CompilationCache * @param path the path parameters of the http message * @param query the query parameters of the http message * @param headers the header parameters of the http message + * @param statusCode the int value of a http response status code */ -case class Metadata( - path: Map[String, String] = Map.empty, - query: Map[String, Seq[String]] = Map.empty, - headers: Map[CaseInsensitive, Seq[String]] = Map.empty, - statusCode: Option[Int] = None +case class Metadata private ( + path: Map[String, String], + query: Map[String, Seq[String]], + headers: Map[CaseInsensitive, Seq[String]], + statusCode: Option[Int] ) { self => + def withPath(value: Map[String, String]): Metadata = { + copy(path = value) + } + + def withQuery(value: Map[String, Seq[String]]): Metadata = { + copy(query = value) + } + + def withHeaders(value: Map[CaseInsensitive, Seq[String]]): Metadata = { + copy(headers = value) + } + + def withStatusCode(value: Option[Int]): Metadata = { + copy(statusCode = value) + } def headersFlattened: Vector[(CaseInsensitive, String)] = headers.toVector.flatMap { case (k, v) => v.map(k -> _) @@ -121,27 +137,38 @@ case class Metadata( def find(location: HttpBinding): Option[(String, List[String])] = location match { - case HttpBinding.HeaderBinding(httpName) => - headers.get(httpName).flatMap { + case hb: HttpBinding.HeaderBinding => + headers.get(hb.httpName).flatMap { case head :: tl => Some((head, tl)) case Nil => None } - case HttpBinding.QueryBinding(httpName) => - query.get(httpName).flatMap { + case qb: HttpBinding.QueryBinding => + query.get(qb.httpName).flatMap { case head :: tl => Some((head, tl)) case Nil => None } - case HttpBinding.PathBinding(httpName) => path.get(httpName).map(_ -> Nil) - case _ => None + case pb: HttpBinding.PathBinding => path.get(pb.httpName).map(_ -> Nil) + case _ => None } } object Metadata { - + @scala.annotation.nowarn( + "msg=private method unapply in object Metadata is never used" + ) + private def unapply(c: Metadata): Option[Metadata] = Some(c) + def apply( + path: Map[String, String] = Map.empty, + query: Map[String, Seq[String]] = Map.empty, + headers: Map[CaseInsensitive, Seq[String]] = Map.empty, + statusCode: Option[Int] = None + ): Metadata = { + new Metadata(path, query, headers, statusCode) + } def fold[A](i: Iterable[A])(f: A => Metadata): Metadata = i.foldLeft(empty)((acc, a) => acc ++ f(a)) - val empty = Metadata(Map.empty, Map.empty, Map.empty) + val empty = Metadata(Map.empty, Map.empty, Map.empty, None) trait Access { def metadata: Metadata diff --git a/modules/core/src/smithy4s/http/PathSegment.scala b/modules/core/src/smithy4s/http/PathSegment.scala index 8f9315064..bbb3c585d 100644 --- a/modules/core/src/smithy4s/http/PathSegment.scala +++ b/modules/core/src/smithy4s/http/PathSegment.scala @@ -23,8 +23,55 @@ object PathSegment { def label(value: String): PathSegment = LabelSegment(value) def greedy(value: String): PathSegment = GreedySegment(value) - case class StaticSegment(value: String) extends PathSegment - case class LabelSegment(value: String) extends PathSegment - case class GreedySegment(value: String) extends PathSegment + case class StaticSegment private (value: String) extends PathSegment { + def withValue(value: String): StaticSegment = { + copy(value = value) + } + + } + object StaticSegment { + @scala.annotation.nowarn( + "msg=private method unapply in object StaticSegment is never used" + ) + private def unapply(c: StaticSegment): Option[StaticSegment] = Some(c) + def apply(value: String): StaticSegment = { + new StaticSegment(value) + } + + } + + case class LabelSegment private (value: String) extends PathSegment { + def withValue(value: String): LabelSegment = { + copy(value = value) + } + + } + object LabelSegment { + @scala.annotation.nowarn( + "msg=private method unapply in object LabelSegment is never used" + ) + private def unapply(c: LabelSegment): Option[LabelSegment] = Some(c) + def apply(value: String): LabelSegment = { + new LabelSegment(value) + } + + } + + case class GreedySegment private (value: String) extends PathSegment { + def withValue(value: String): GreedySegment = { + copy(value = value) + } + + } + object GreedySegment { + @scala.annotation.nowarn( + "msg=private method unapply in object GreedySegment is never used" + ) + private def unapply(c: GreedySegment): Option[GreedySegment] = Some(c) + def apply(value: String): GreedySegment = { + new GreedySegment(value) + } + + } } diff --git a/modules/core/src/smithy4s/http/UnknownErrorResponse.scala b/modules/core/src/smithy4s/http/UnknownErrorResponse.scala index 6f0152798..9269dc8c2 100644 --- a/modules/core/src/smithy4s/http/UnknownErrorResponse.scala +++ b/modules/core/src/smithy4s/http/UnknownErrorResponse.scala @@ -16,11 +16,39 @@ package smithy4s.http -case class UnknownErrorResponse( +case class UnknownErrorResponse private ( code: Int, headers: Map[CaseInsensitive, Seq[String]], body: String ) extends Throwable { + def withCode(value: Int): UnknownErrorResponse = { + copy(code = value) + } + + def withHeaders( + value: Map[CaseInsensitive, Seq[String]] + ): UnknownErrorResponse = { + copy(headers = value) + } + + def withBody(value: String): UnknownErrorResponse = { + copy(body = value) + } override def getMessage(): String = s"status $code, headers: $headers, body:\n$body" } + +object UnknownErrorResponse { + @scala.annotation.nowarn( + "msg=private method unapply in object UnknownErrorResponse is never used" + ) + private def unapply(c: UnknownErrorResponse): Option[UnknownErrorResponse] = + Some(c) + def apply( + code: Int, + headers: Map[CaseInsensitive, Seq[String]], + body: String + ): UnknownErrorResponse = { + new UnknownErrorResponse(code, headers, body) + } +} diff --git a/modules/core/src/smithy4s/http/UrlForm.scala b/modules/core/src/smithy4s/http/UrlForm.scala index 47641e732..ee3fef87b 100644 --- a/modules/core/src/smithy4s/http/UrlForm.scala +++ b/modules/core/src/smithy4s/http/UrlForm.scala @@ -36,8 +36,11 @@ import scala.collection.immutable.BitSet import scala.collection.mutable /** Represents data that was encoded using the `application/x-www-form-urlencoded` format. */ -final case class UrlForm(values: List[UrlForm.FormData]) { +final case class UrlForm private (values: List[UrlForm.FormData]) { + def withValues(value: List[UrlForm.FormData]): UrlForm = { + copy(values = value) + } def render: String = { val builder = new mutable.StringBuilder val lastIndex = values.size - 1 @@ -52,9 +55,25 @@ final case class UrlForm(values: List[UrlForm.FormData]) { } object UrlForm { + @scala.annotation.nowarn( + "msg=private method unapply in object UrlForm is never used" + ) + private def unapply(c: UrlForm): Option[UrlForm] = Some(c) + def apply(values: List[UrlForm.FormData]): UrlForm = { + new UrlForm(values) + } + final case class FormData private ( + path: PayloadPath, + maybeValue: Option[String] + ) { - final case class FormData(path: PayloadPath, maybeValue: Option[String]) { + def withPath(value: PayloadPath): FormData = { + copy(path = value) + } + def withMaybeValue(value: Option[String]): FormData = { + copy(maybeValue = value) + } def prepend(segment: PayloadPath.Segment): FormData = copy(path.prepend(segment), maybeValue) @@ -63,11 +82,11 @@ object UrlForm { var i = 0 for (segment <- path.segments) { builder.append(segment match { - case Segment.Label(label) => - URLEncoder.encode(label, StandardCharsets.UTF_8.name()) + case l: Segment.Label => + URLEncoder.encode(l.label, StandardCharsets.UTF_8.name()) - case Segment.Index(index) => - index + case i: Segment.Index => + i.index }) if (i < lastIndex) builder.append('.') i += 1 @@ -80,6 +99,15 @@ object UrlForm { ) } } + object FormData { + @scala.annotation.nowarn( + "msg=private method unapply in object FormData is never used" + ) + private def unapply(c: FormData): Option[FormData] = Some(c) + def apply(path: PayloadPath, maybeValue: Option[String]): FormData = { + new FormData(path, maybeValue) + } + } /** Parses a `application/x-www-form-urlencoded` formatted String into a [[UrlForm]]. */ def parse( diff --git a/modules/core/src/smithy4s/http/UrlFormDecodeError.scala b/modules/core/src/smithy4s/http/UrlFormDecodeError.scala index 3428f5d07..59c78ea7b 100644 --- a/modules/core/src/smithy4s/http/UrlFormDecodeError.scala +++ b/modules/core/src/smithy4s/http/UrlFormDecodeError.scala @@ -20,16 +20,34 @@ package http import smithy4s.codecs.PayloadPath import smithy4s.http.internals.UrlFormCursor -final case class UrlFormDecodeError( +final case class UrlFormDecodeError private ( path: PayloadPath, message: String ) extends Throwable { + def withPath(value: PayloadPath): UrlFormDecodeError = { + copy(path = value) + } + + def withMessage(value: String): UrlFormDecodeError = { + copy(message = value) + } override def getMessage(): String = s"${path.render()}: $message" } -private[http] object UrlFormDecodeError { +object UrlFormDecodeError { + @scala.annotation.nowarn( + "msg=private method unapply in object UrlFormDecodeError is never used" + ) + private def unapply(c: UrlFormDecodeError): Option[UrlFormDecodeError] = Some( + c + ) + def apply(path: PayloadPath, message: String): UrlFormDecodeError = { + new UrlFormDecodeError(path, message) + } - def singleValueExpected(cursor: UrlFormCursor): UrlFormDecodeError = + private[http] def singleValueExpected( + cursor: UrlFormCursor + ): UrlFormDecodeError = UrlFormDecodeError( cursor.history, s"Expected a single value but got ${cursor.values}" diff --git a/modules/core/src/smithy4s/http/internals/MetaDecode.scala b/modules/core/src/smithy4s/http/internals/MetaDecode.scala index 2424fac5f..0d6a317c3 100644 --- a/modules/core/src/smithy4s/http/internals/MetaDecode.scala +++ b/modules/core/src/smithy4s/http/internals/MetaDecode.scala @@ -50,40 +50,44 @@ private[http] sealed abstract class MetaDecode[+A] { m(metadata).get(key) match { case Some(value) => process(value, fieldName, putField(_)) case None if maybeDefault.isDefined => putField(maybeDefault.get) - case None => throw new MetadataError.NotFound(fieldName, binding) + case None => throw MetadataError.NotFound(fieldName, binding) } } // format: on def putDefault(putField: PutField) = maybeDefault match { case Some(default) => putField(default) - case None => throw new MetadataError.NotFound(fieldName, binding) + case None => throw MetadataError.NotFound(fieldName, binding) } (binding, this) match { - case (PathBinding(path), StringValueMetaDecode(f)) => - lookupAndProcess(_.path, path) { (value, fieldName, putField) => + case (pb: PathBinding, StringValueMetaDecode(f)) => + lookupAndProcess(_.path, pb.httpName) { (value, fieldName, putField) => putField(f(value)) } - case (HeaderBinding(h), StringValueMetaDecode(f)) => - lookupAndProcess(_.headers, h) { (values, fieldName, putField) => - if (values.size == 1) { - putField(f(values.head)) - } else throw MetadataError.ArityError(fieldName, binding) + case (hb: HeaderBinding, StringValueMetaDecode(f)) => + lookupAndProcess(_.headers, hb.httpName) { + (values, fieldName, putField) => + if (values.size == 1) { + putField(f(values.head)) + } else throw MetadataError.ArityError(fieldName, binding) } - case (HeaderBinding(h), StringCollectionMetaDecode(f)) => - lookupAndProcess(_.headers, h) { (values, fieldName, putField) => - putField(f(values.iterator)) + case (hb: HeaderBinding, StringCollectionMetaDecode(f)) => + lookupAndProcess(_.headers, hb.httpName) { + (values, fieldName, putField) => + putField(f(values.iterator)) } - case (QueryBinding(h), StringValueMetaDecode(f)) => - lookupAndProcess(_.query, h) { (values, fieldName, putField) => - if (values.size == 1) { - putField(f(values.head)) - } else throw MetadataError.ArityError(fieldName, binding) + case (qb: QueryBinding, StringValueMetaDecode(f)) => + lookupAndProcess(_.query, qb.httpName) { + (values, fieldName, putField) => + if (values.size == 1) { + putField(f(values.head)) + } else throw MetadataError.ArityError(fieldName, binding) } - case (QueryBinding(q), StringCollectionMetaDecode(f)) => - lookupAndProcess(_.query, q) { (values, fieldName, putField) => - putField(f(values.iterator)) + case (qb: QueryBinding, StringCollectionMetaDecode(f)) => + lookupAndProcess(_.query, qb.httpName) { + (values, fieldName, putField) => + putField(f(values.iterator)) } // see https://smithy.io/2.0/spec/http-bindings.html#httpqueryparams-trait // when targeting Map[String,String] we take the first value encountered @@ -107,25 +111,25 @@ private[http] sealed abstract class MetaDecode[+A] { if (iter.nonEmpty) putField(f(iter)) else putDefault(putField) } - case (HeaderPrefixBinding(prefix), StringMapMetaDecode(f)) => { + case (hpb: HeaderPrefixBinding, StringMapMetaDecode(f)) => { (metadata, putField) => val iter = metadata.headers.iterator .collect { - case (k, values) if k.startsWith(prefix) => + case (k, values) if k.startsWith(hpb.prefix) => if (values.size == 1) { - k.toString.drop(prefix.length()) -> values.head + k.toString.drop(hpb.prefix.length()) -> values.head } else throw MetadataError.ArityError(fieldName, HeaderBinding(k)) } if (iter.nonEmpty) putField(f(iter)) else putDefault(putField) } - case (HeaderPrefixBinding(prefix), StringListMapMetaDecode(f)) => { + case (hpb: HeaderPrefixBinding, StringListMapMetaDecode(f)) => { (metadata, putField) => val iter = metadata.headers.iterator .collect { - case (k, values) if k.startsWith(prefix) => - k.toString.drop(prefix.length()) -> values.iterator + case (k, values) if k.startsWith(hpb.prefix) => + k.toString.drop(hpb.prefix.length()) -> values.iterator } if (iter.nonEmpty) putField(f(iter)) else putDefault(putField) diff --git a/modules/core/src/smithy4s/http/internals/MetaEncode.scala b/modules/core/src/smithy4s/http/internals/MetaEncode.scala index 02e3e3922..b55c4d54b 100644 --- a/modules/core/src/smithy4s/http/internals/MetaEncode.scala +++ b/modules/core/src/smithy4s/http/internals/MetaEncode.scala @@ -31,17 +31,18 @@ sealed trait MetaEncode[-A] { binding: HttpBinding ): (Metadata, A) => Metadata = (binding, this) match { - case (PathBinding(path), StringValueMetaEncode(f)) => - (metadata: Metadata, a: A) => metadata.addPathParam(path, f(a)) - case (HeaderBinding(name), StringValueMetaEncode(f)) => - (metadata: Metadata, a: A) => metadata.addHeader(name, f(a)) - case (HeaderBinding(name), StringListMetaEncode(f)) => - (metadata: Metadata, a: A) => metadata.addMultipleHeaders(name, f(a)) - case (QueryBinding(name), StringValueMetaEncode(f)) => - (metadata: Metadata, a: A) => metadata.addQueryParam(name, f(a)) - case (QueryBinding(name), StringListMetaEncode(f)) => + case (pb: PathBinding, StringValueMetaEncode(f)) => + (metadata: Metadata, a: A) => metadata.addPathParam(pb.httpName, f(a)) + case (hb: HeaderBinding, StringValueMetaEncode(f)) => + (metadata: Metadata, a: A) => metadata.addHeader(hb.httpName, f(a)) + case (hb: HeaderBinding, StringListMetaEncode(f)) => (metadata: Metadata, a: A) => - metadata.addMultipleQueryParams(name, f(a)) + metadata.addMultipleHeaders(hb.httpName, f(a)) + case (qb: QueryBinding, StringValueMetaEncode(f)) => + (metadata: Metadata, a: A) => metadata.addQueryParam(qb.httpName, f(a)) + case (qb: QueryBinding, StringListMetaEncode(f)) => + (metadata: Metadata, a: A) => + metadata.addMultipleQueryParams(qb.httpName, f(a)) case (QueryParamsBinding, StringMapMetaEncode(f)) => (metadata: Metadata, a: A) => f(a).foldLeft(metadata) { case (m, (k, v)) => @@ -52,15 +53,15 @@ sealed trait MetaEncode[-A] { f(a).foldLeft(metadata) { case (m, (k, v)) => m.addQueryParamsIfNoExist(k, v: _*) } - case (HeaderPrefixBinding(prefix), StringMapMetaEncode(f)) => + case (hpb: HeaderPrefixBinding, StringMapMetaEncode(f)) => (metadata: Metadata, a: A) => f(a).foldLeft(metadata) { case (m, (k, v)) => - m.addHeader(prefix + k, v) + m.addHeader(hpb.prefix + k, v) } - case (HeaderPrefixBinding(prefix), StringListMapMetaEncode(f)) => + case (hpb: HeaderPrefixBinding, StringListMapMetaEncode(f)) => (metadata: Metadata, a: A) => f(a).foldLeft(metadata) { case (m, (k, v)) => - m.addMultipleHeaders(prefix + k, v) + m.addMultipleHeaders(hpb.prefix + k, v) } case _ => (metadata: Metadata, _: A) => metadata } diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala index 790708b62..5b1d1dcab 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorMetadataReader.scala @@ -126,16 +126,16 @@ private[http] class SchemaVisitorMetadataReader( tag match { case EnumTag.ClosedIntEnum => MetaDecode.from(intVals)(str => handleInt(str.toIntOption)) - case EnumTag.OpenIntEnum(unknown) => + case oie: EnumTag.OpenIntEnum[_] => MetaDecode.from(intVals) { string => val maybeInt = string.toIntOption - handleInt(maybeInt).orElse(maybeInt.map(unknown)) + handleInt(maybeInt).orElse(maybeInt.map(oie.unknown)) } case EnumTag.ClosedStringEnum => MetaDecode.from(stringVals)(handleString) - case EnumTag.OpenStringEnum(unknown) => + case ose: EnumTag.OpenStringEnum[_] => MetaDecode.from(stringVals)(str => - Some(handleString(str).getOrElse(unknown(str))) + Some(handleString(str).getOrElse(ose.unknown(str))) ) } } @@ -194,12 +194,12 @@ private[http] class SchemaVisitorMetadataReader( case e: MetadataError => Left(e) case MetaDecode.MetaDecodeError(const) => Left(const(currentFieldName, currentBinding)) - case ConstraintError(_, message) => + case ce: ConstraintError => Left( MetadataError.FailedConstraint( currentFieldName, currentBinding, - message + ce.message ) ) } diff --git a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala index ad9cd0b69..a2512f8e7 100644 --- a/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala +++ b/modules/core/src/smithy4s/http/internals/SchemaVisitorPathEncoder.scala @@ -96,14 +96,14 @@ object SchemaVisitorPathEncoder else writer.map(_.encode) } def compile1(path: PathSegment): Option[Writer] = path match { - case StaticSegment(value) => Some(Function.const(List(value))) - case LabelSegment(value) => + case ss: StaticSegment => Some(Function.const(List(ss.value))) + case ls: LabelSegment => fields - .find(_.label == value) + .find(_.label == ls.value) .flatMap(field => toPathEncoder(field, greedy = false)) - case GreedySegment(value) => + case gs: GreedySegment => fields - .find(_.label == value) + .find(_.label == gs.value) .flatMap(field => toPathEncoder(field, greedy = true)) } diff --git a/modules/core/src/smithy4s/http/internals/UrlFormCursor.scala b/modules/core/src/smithy4s/http/internals/UrlFormCursor.scala index 4178fd264..a9ad8a6a6 100644 --- a/modules/core/src/smithy4s/http/internals/UrlFormCursor.scala +++ b/modules/core/src/smithy4s/http/internals/UrlFormCursor.scala @@ -38,9 +38,13 @@ private[http] final case class UrlFormCursor( UrlFormCursor( history.append(segment), values.collect { - case UrlForm - .FormData(PayloadPath(`segment` :: segments), Some(value)) => - UrlForm.FormData(PayloadPath(segments), Some(value)) + case uf: UrlForm.FormData + if uf.path.segments.headOption.contains(segment) && + uf.maybeValue.isDefined => + UrlForm.FormData( + PayloadPath(uf.path.segments.tail: _*), + uf.maybeValue + ) } ) diff --git a/modules/core/src/smithy4s/http/internals/UrlFormDataDecoder.scala b/modules/core/src/smithy4s/http/internals/UrlFormDataDecoder.scala index 833b1f272..2e6ec3eef 100644 --- a/modules/core/src/smithy4s/http/internals/UrlFormDataDecoder.scala +++ b/modules/core/src/smithy4s/http/internals/UrlFormDataDecoder.scala @@ -55,8 +55,9 @@ private[internals] object UrlFormDataDecoder { ): UrlFormDataDecoder[A] = { case UrlFormCursor( history, - List(UrlForm.FormData(PayloadPath.root, Some(value))) - ) => + List(uf: UrlForm.FormData) + ) if uf.path == PayloadPath.root && uf.maybeValue.isDefined => + val value = uf.maybeValue.get f(value).toRight( UrlFormDecodeError( history, diff --git a/modules/core/src/smithy4s/http/internals/UrlFormDataDecoderSchemaVisitor.scala b/modules/core/src/smithy4s/http/internals/UrlFormDataDecoderSchemaVisitor.scala index 99d825e01..ef0bd25cf 100644 --- a/modules/core/src/smithy4s/http/internals/UrlFormDataDecoderSchemaVisitor.scala +++ b/modules/core/src/smithy4s/http/internals/UrlFormDataDecoderSchemaVisitor.scala @@ -34,6 +34,19 @@ private[http] class UrlFormDataDecoderSchemaVisitor( with smithy4s.ScalaCompat { compile => + private object singleSegment { + def unapply(c: UrlForm.FormData): Option[PayloadPath.Segment] = + if (c.path.segments.size == 1) c.path.segments.headOption + else None + } + private object firstSegmentIsIndex { + def unapply(c: UrlForm.FormData): Option[Int] = { + c.path.segments.headOption.collectFirst { + case i: PayloadPath.Segment.Index => i.index + } + } + } + override def primitive[P]( shapeId: ShapeId, hints: Hints, @@ -77,12 +90,8 @@ private[http] class UrlFormDataDecoderSchemaVisitor( // then sort by index. import scala.collection.compat._ val groupedAndSortedCursors = values - .collect { - case formData @ UrlForm.FormData( - PayloadPath(PayloadPath.Segment.Index(index) :: _), - _ - ) => - index -> formData + .collect { case formData @ firstSegmentIsIndex(index) => + index -> formData } .groupMap { case (index, _) => index } { case (_, value) => value } .toVector @@ -170,7 +179,7 @@ private[http] class UrlFormDataDecoderSchemaVisitor( locally { case cursor @ UrlFormCursor( history, - UrlForm.FormData(PayloadPath(segment :: Nil), _) :: Nil + singleSegment(segment) :: Nil ) => altMap.get(segment) match { case Some(altDecoder) => diff --git a/modules/core/src/smithy4s/http/matchPath.scala b/modules/core/src/smithy4s/http/matchPath.scala index e366f186b..c9403e360 100644 --- a/modules/core/src/smithy4s/http/matchPath.scala +++ b/modules/core/src/smithy4s/http/matchPath.scala @@ -35,23 +35,23 @@ object matchPath extends smithy4s.ScalaCompat { ): Option[Map[String, String]] = path match { case Nil if i >= size => Some(acc) - case StaticSegment(value) :: lt - if i < size && compareStrings(value, received(i)) => + case (ss: StaticSegment) :: lt + if i < size && compareStrings(ss.value, received(i)) => matchPathAux(lt, i + 1, acc, Nil) - case (LabelSegment(name) :: lt) if i < size => - matchPathAux(lt, i + 1, acc + (name -> received(i)), Nil) - case (GreedySegment(name) :: StaticSegment(value) :: lt) + case (ls: LabelSegment) :: lt if i < size => + matchPathAux(lt, i + 1, acc + (ls.value -> received(i)), Nil) + case (gs: GreedySegment) :: (ss: StaticSegment) :: lt if i < size && compareStrings( - value, + ss.value, received(i) ) && greedyAcc.nonEmpty => val value = greedyAcc.reverse.mkString("/") - matchPathAux(lt, i + 1, acc + (name -> value), Nil) - case p @ (GreedySegment(_) :: Nil) if i < size => + matchPathAux(lt, i + 1, acc + (gs.value -> value), Nil) + case p @ ((_: GreedySegment) :: Nil) if i < size => matchPathAux(p, i + 1, acc, received(i) :: greedyAcc) - case GreedySegment(name) :: Nil if greedyAcc.nonEmpty => + case (gs: GreedySegment) :: Nil if greedyAcc.nonEmpty => val value = greedyAcc.reverse.mkString("/") - Some(acc + (name -> value)) + Some(acc + (gs.value -> value)) case _ => None } matchPathAux(path, 0, Map.empty, List.empty) diff --git a/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala b/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala index e12577f13..dfa7e31b7 100644 --- a/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala +++ b/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala @@ -53,7 +53,11 @@ trait DocumentDecoder[A] { self => f(self(path, document)) match { case Right(value) => value case Left(value) => - throw PayloadError(PayloadPath(path), expected, value.message) + throw PayloadError( + PayloadPath.fromSegments(path), + expected, + value.message + ) } } @@ -80,7 +84,7 @@ object DocumentDecoder { if (f.isDefinedAt(tuple)) f(tuple) else throw PayloadError( - PayloadPath(history.reverse), + PayloadPath.fromSegments(history.reverse), expectedType, s"Expected Json Shape: $expectedJsonShape but got the following Json Shape ${document.name}" ) @@ -170,8 +174,8 @@ class DocumentDecoderSchemaVisitor( Timestamp .parse(value, format) .getOrElse( - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), formatRepr, s"Wrong timestamp format" ) @@ -235,7 +239,7 @@ class DocumentDecoderSchemaVisitor( { case DocumentKeyDecoder.DecodeError(expectedType) => val path = PayloadPath.Segment.parse(key) :: pp throw PayloadError( - PayloadPath(path.reverse), + PayloadPath.fromSegments(path.reverse), expectedType, "Wrong Json shape" ) @@ -262,8 +266,8 @@ class DocumentDecoderSchemaVisitor( builder.+=((decodedKey, decodedValue)) i += 1 case _ => - throw new PayloadError( - PayloadPath((PayloadPath.Segment(i) :: pp).reverse), + throw PayloadError( + PayloadPath((PayloadPath.Segment(i) :: pp).reverse: _*), "Key Value object", """Expected a Json object containing two values indexed with "key" and "value". """ ) @@ -290,17 +294,17 @@ class DocumentDecoderSchemaVisitor( case DNumber(value) if fromOrdinal.contains(value) => fromOrdinal(value) } - case EnumTag.OpenIntEnum(unknown) => + case oie: EnumTag.OpenIntEnum[_] => from(label) { case DNumber(value) => - fromOrdinal.getOrElse(value, unknown(value.toInt)) + fromOrdinal.getOrElse(value, oie.unknown(value.toInt)) } case EnumTag.ClosedStringEnum => from(label) { case DString(value) if fromName.contains(value) => fromName(value) } - case EnumTag.OpenStringEnum(unknown) => + case ose: EnumTag.OpenStringEnum[_] => from(label) { case DString(value) => - fromName.getOrElse(value, unknown(value)) + fromName.getOrElse(value, ose.unknown(value)) } } } @@ -350,8 +354,8 @@ class DocumentDecoderSchemaVisitor( case Some(document) => buffer(apply(field.schema)(path, document)) case None => - throw new PayloadError( - PayloadPath(path.reverse), + throw PayloadError( + PayloadPath.fromSegments(path.reverse), "", "Required field not found" ) @@ -394,22 +398,22 @@ class DocumentDecoderSchemaVisitor( decoders.get(value.value) match { case Some(decoder) => decoder(pp, document) case None => - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), "Union", s"Unknown discriminator: ${value.value}" ) } case _ => - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), "Union", s"Unable to locate discriminator under property '${discriminated.value}'" ) } case other => - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), "Union", s"Expected DObject, but found $other" ) @@ -427,15 +431,15 @@ class DocumentDecoderSchemaVisitor( decoders.get(key) match { case Some(decoder) => decoder(pp, value) case None => - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), "Union", s"Unknown discriminator: $key" ) } case _ => - throw new PayloadError( - PayloadPath(pp.reverse), + throw PayloadError( + PayloadPath.fromSegments(pp.reverse), "Union", "Expected a single-key Json object" ) @@ -453,10 +457,10 @@ class DocumentDecoderSchemaVisitor( alt.schema.hints.get(JsonName).map(_.value).getOrElse(alt.label) val decoders: DecoderMap[U] = - alternatives.map { case alt @ Alt(_, instance, inject, _) => + alternatives.map { case alt: Alt[_, _] => val label = jsonLabel(alt) val encoder = { (pp: List[PayloadPath.Segment], doc: Document) => - inject(apply(instance)(label :: pp, doc)) + alt.inject(apply(alt.schema)(label :: pp, doc)) } jsonLabel(alt) -> encoder }.toMap @@ -516,7 +520,7 @@ class DocumentDecoderSchemaVisitor( if (f.isDefinedAt(a)) f(a) else throw PayloadError( - PayloadPath(path.reverse), + PayloadPath.fromSegments(path.reverse), expectedType, "Wrong Json shape" ) @@ -535,14 +539,14 @@ class DocumentDecoderSchemaVisitor( } catch { case e: Throwable => throw PayloadError( - PayloadPath(path.reverse), + PayloadPath.fromSegments(path.reverse), expectedType, e.getMessage() ) } } else throw PayloadError( - PayloadPath(path.reverse), + PayloadPath.fromSegments(path.reverse), expectedType, "Wrong Json shape" ) diff --git a/modules/core/src/smithy4s/internals/DocumentKeyDecoder.scala b/modules/core/src/smithy4s/internals/DocumentKeyDecoder.scala index cce839c38..cb1c7b1ea 100644 --- a/modules/core/src/smithy4s/internals/DocumentKeyDecoder.scala +++ b/modules/core/src/smithy4s/internals/DocumentKeyDecoder.scala @@ -139,11 +139,11 @@ object DocumentKeyDecoder { val intVal = s"value in [${fromNum.keySet.mkString(", ")}]" val stringVal = s"value in [${fromName.keySet.mkString(", ")}]" tag match { - case EnumTag.OpenIntEnum(unknown) => + case oie: EnumTag.OpenIntEnum[_] => from(intVal) { case DString(value) if value.toIntOption.isDefined => val i = value.toInt - fromNum.getOrElse(i, unknown(i)) + fromNum.getOrElse(i, oie.unknown(i)) } case EnumTag.ClosedIntEnum => from(intVal) { @@ -151,9 +151,9 @@ object DocumentKeyDecoder { if value.toIntOption.exists(fromNum.contains(_)) => fromNum(value.toInt) } - case EnumTag.OpenStringEnum(unknown) => + case ose: EnumTag.OpenStringEnum[_] => from(stringVal) { case DString(value) => - fromName.getOrElse(value, unknown(value)) + fromName.getOrElse(value, ose.unknown(value)) } case EnumTag.ClosedStringEnum => from(stringVal) { diff --git a/modules/core/src/smithy4s/internals/SchemaVisitorPatternDecoder.scala b/modules/core/src/smithy4s/internals/SchemaVisitorPatternDecoder.scala index ae2f26d35..3d12fd312 100644 --- a/modules/core/src/smithy4s/internals/SchemaVisitorPatternDecoder.scala +++ b/modules/core/src/smithy4s/internals/SchemaVisitorPatternDecoder.scala @@ -65,7 +65,7 @@ private[internals] final class SchemaVisitorPatternDecoder( fromOrdinal(BigDecimal(value)) else throw StructurePatternError(s"Enum case for '$value' not found.") ) - case EnumTag.OpenIntEnum(unknown) => + case oie: EnumTag.OpenIntEnum[_] => PatternDecode.from(value => if (fromOrdinal.contains(BigDecimal(value))) fromOrdinal(BigDecimal(value)) @@ -74,7 +74,7 @@ private[internals] final class SchemaVisitorPatternDecoder( util .Try(value.toInt) .toOption - .map(unknown(_)) + .map(oie.unknown(_)) .getOrElse( throw StructurePatternError( s"Enum case for '$value' not found." @@ -86,10 +86,10 @@ private[internals] final class SchemaVisitorPatternDecoder( if (fromName.contains(value)) fromName(value) else throw StructurePatternError(s"Enum case for '$value' not found.") ) - case EnumTag.OpenStringEnum(unknown) => + case ose: EnumTag.OpenStringEnum[_] => PatternDecode.from(value => if (fromName.contains(value)) fromName(value) - else unknown(value) + else ose.unknown(value) ) } } diff --git a/modules/core/src/smithy4s/schema/Alt.scala b/modules/core/src/smithy4s/schema/Alt.scala index 81d338798..da28135ba 100644 --- a/modules/core/src/smithy4s/schema/Alt.scala +++ b/modules/core/src/smithy4s/schema/Alt.scala @@ -24,13 +24,28 @@ import kinds._ /** * Represents a member of coproduct type (sealed trait) */ -final case class Alt[U, A]( +final case class Alt[U, A] private ( label: String, schema: Schema[A], inject: A => U, project: PartialFunction[U, A] ) { + def withLabel(value: String): Alt[U, A] = { + copy(label = value) + } + + def withSchema(value: Schema[A]): Alt[U, A] = { + copy(schema = value) + } + + def withInject(value: A => U): Alt[U, A] = { + copy(inject = value) + } + + def withProject(value: PartialFunction[U, A]): Alt[U, A] = { + copy(project = value) + } @deprecated("use .schema instead", since = "0.18.0") def instance: Schema[A] = schema @@ -56,6 +71,18 @@ final case class Alt[U, A]( } object Alt { + @scala.annotation.nowarn( + "msg=private method unapply in object Alt is never used" + ) + private def unapply[U, A](c: Alt[U, A]): Option[Alt[U, A]] = Some(c) + def apply[U, A]( + label: String, + schema: Schema[A], + inject: A => U, + project: PartialFunction[U, A] + ): Alt[U, A] = { + new Alt(label, schema, inject, project) + } /** * Precompiles an Alt to produce an instance of `G` diff --git a/modules/core/src/smithy4s/schema/EnumTag.scala b/modules/core/src/smithy4s/schema/EnumTag.scala index 48b4456d8..d88cbeffd 100644 --- a/modules/core/src/smithy4s/schema/EnumTag.scala +++ b/modules/core/src/smithy4s/schema/EnumTag.scala @@ -21,23 +21,56 @@ sealed trait EnumTag[+E] object EnumTag { case object ClosedStringEnum extends EnumTag[Nothing] case object ClosedIntEnum extends EnumTag[Nothing] - case class OpenStringEnum[E](unknown: String => E) extends EnumTag[E] - case class OpenIntEnum[E](unknown: Int => E) extends EnumTag[E] + + case class OpenStringEnum[E] private (unknown: String => E) + extends EnumTag[E] { + def withUnknown(value: String => E): OpenStringEnum[E] = { + copy(unknown = value) + } + + } + object OpenStringEnum { + @scala.annotation.nowarn( + "msg=private method unapply in object OpenStringEnum is never used" + ) + private def unapply[E](c: OpenStringEnum[E]): Option[OpenStringEnum[E]] = + Some( + c + ) + def apply[E](unknown: String => E): OpenStringEnum[E] = { + new OpenStringEnum(unknown) + } + } + + case class OpenIntEnum[E] private (unknown: Int => E) extends EnumTag[E] { + def withUnknown(value: Int => E): OpenIntEnum[E] = { + copy(unknown = value) + } + + } + object OpenIntEnum { + @scala.annotation.nowarn( + "msg=private method unapply in object OpenIntEnum is never used" + ) + private def unapply[E](c: OpenIntEnum[E]): Option[OpenIntEnum[E]] = Some(c) + def apply[E](unknown: Int => E): OpenIntEnum[E] = { + new OpenIntEnum(unknown) + } + } object StringEnum { def unapply[E](enumTag: EnumTag[E]): Boolean = enumTag match { - case ClosedStringEnum => true - case OpenStringEnum(_) => true - case _ => false + case ClosedStringEnum => true + case _: OpenStringEnum[_] => true + case _ => false } - } object IntEnum { def unapply[E](enumTag: EnumTag[E]): Boolean = enumTag match { - case ClosedIntEnum => true - case OpenIntEnum(_) => true - case _ => false + case ClosedIntEnum => true + case _: OpenIntEnum[_] => true + case _ => false } } } diff --git a/modules/core/src/smithy4s/schema/EnumValue.scala b/modules/core/src/smithy4s/schema/EnumValue.scala index b6915f0a8..fc0fffc8e 100644 --- a/modules/core/src/smithy4s/schema/EnumValue.scala +++ b/modules/core/src/smithy4s/schema/EnumValue.scala @@ -17,16 +17,52 @@ package smithy4s package schema -case class EnumValue[E]( +case class EnumValue[E] private ( stringValue: String, intValue: Int, value: E, name: String, hints: Hints ) { + def withStringValue(value: String): EnumValue[E] = { + copy(stringValue = value) + } + + def withIntValue(value: Int): EnumValue[E] = { + copy(intValue = value) + } + + def withValue(value: E): EnumValue[E] = { + copy(value = value) + } + + def withName(value: String): EnumValue[E] = { + copy(name = value) + } + + def withHints(value: Hints): EnumValue[E] = { + copy(hints = value) + } def map[A](f: E => A): EnumValue[A] = copy(value = f(value)) def transformHints(f: Hints => Hints): EnumValue[E] = copy(hints = f(hints)) } + +object EnumValue { + @scala.annotation.nowarn( + "msg=private method unapply in object EnumValue is never used" + ) + private def unapply[E](c: EnumValue[E]): Option[EnumValue[E]] = Some(c) + def apply[E]( + stringValue: String, + intValue: Int, + value: E, + name: String, + hints: Hints + ): EnumValue[E] = { + new EnumValue(stringValue, intValue, value, name, hints) + } + +} diff --git a/modules/core/src/smithy4s/schema/ErrorSchema.scala b/modules/core/src/smithy4s/schema/ErrorSchema.scala index b2bf206b2..64bc391bf 100644 --- a/modules/core/src/smithy4s/schema/ErrorSchema.scala +++ b/modules/core/src/smithy4s/schema/ErrorSchema.scala @@ -34,6 +34,17 @@ case class ErrorSchema[E] private[smithy4s] ( unliftError: E => Throwable ) { + def withSchema(value: Schema[E]): ErrorSchema[E] = { + copy(schema = value) + } + + def withLiftError(value: Throwable => Option[E]): ErrorSchema[E] = { + copy(liftError = value) + } + + def withUnliftError(value: E => Throwable): ErrorSchema[E] = { + copy(unliftError = value) + } def transformHintsLocally(f: Hints => Hints): ErrorSchema[E] = { val newSchema = schema match { case u: Schema.UnionSchema[E] => @@ -74,6 +85,17 @@ case class ErrorSchema[E] private[smithy4s] ( object ErrorSchema { + @scala.annotation.nowarn( + "msg=private method unapply in object ErrorSchema is never used" + ) + private def unapply[E](c: ErrorSchema[E]): Option[ErrorSchema[E]] = Some(c) + def apply[E]( + schema: Schema[E], + liftError: Throwable => Option[E], + unliftError: E => Throwable + ): ErrorSchema[E] = { + new ErrorSchema(schema, liftError, unliftError) + } trait Companion[E] extends ShapeTag.Companion[E] { def liftError(throwable: Throwable): Option[E] def unliftError(e: E): Throwable diff --git a/modules/core/src/smithy4s/schema/Field.scala b/modules/core/src/smithy4s/schema/Field.scala index 510ba91ea..191bcacdc 100644 --- a/modules/core/src/smithy4s/schema/Field.scala +++ b/modules/core/src/smithy4s/schema/Field.scala @@ -20,7 +20,7 @@ package schema /** * Represents a member of product type (case class) */ -final case class Field[S, A]( +final case class Field[S, A] private ( label: String, schema: Schema[A], get: S => A @@ -30,6 +30,17 @@ final case class Field[S, A]( * Returns the hints that are only relative to the field * (typically derived from member-level traits) */ + def withLabel(value: String): Field[S, A] = { + copy(label = value) + } + + def withSchema(value: Schema[A]): Field[S, A] = { + copy(schema = value) + } + + def withGet(value: S => A): Field[S, A] = { + copy(get = value) + } final def memberHints: Hints = schema.hints.memberHints /** @@ -91,6 +102,17 @@ final case class Field[S, A]( object Field { + @scala.annotation.nowarn( + "msg=private method unapply in object Field is never used" + ) + private def unapply[S, A](c: Field[S, A]): Option[Field[S, A]] = Some(c) + def apply[S, A]( + label: String, + schema: Schema[A], + get: S => A + ): Field[S, A] = { + new Field(label, schema, get) + } def required[S, A]( label: String, schema: Schema[A], diff --git a/modules/core/src/smithy4s/schema/OperationSchema.scala b/modules/core/src/smithy4s/schema/OperationSchema.scala index 1e193c592..de757fd75 100644 --- a/modules/core/src/smithy4s/schema/OperationSchema.scala +++ b/modules/core/src/smithy4s/schema/OperationSchema.scala @@ -96,3 +96,32 @@ final case class OperationSchema[I, E, O, SI, SO] private[smithy4s] ( copy(streamedOutput = streamedOutput.map(f)) } + +object OperationSchema { + @scala.annotation.nowarn( + "msg=private method unapply in object OperationSchema is never used" + ) + private def unapply[I, E, O, SI, SO]( + c: OperationSchema[I, E, O, SI, SO] + ): Option[OperationSchema[I, E, O, SI, SO]] = Some(c) + def apply[I, E, O, SI, SO]( + id: ShapeId, + hints: Hints, + input: Schema[I], + error: Option[ErrorSchema[E]], + output: Schema[O], + streamedInput: Option[StreamingSchema[SI]], + streamedOutput: Option[StreamingSchema[SO]] + ): OperationSchema[I, E, O, SI, SO] = { + new OperationSchema( + id, + hints, + input, + error, + output, + streamedInput, + streamedOutput + ) + } + +} diff --git a/modules/core/src/smithy4s/schema/SchemaPartition.scala b/modules/core/src/smithy4s/schema/SchemaPartition.scala index 36972fe80..9ade72379 100644 --- a/modules/core/src/smithy4s/schema/SchemaPartition.scala +++ b/modules/core/src/smithy4s/schema/SchemaPartition.scala @@ -32,8 +32,6 @@ sealed trait SchemaPartition[A] object SchemaPartition { - - // format: off /** * Indicates that all fields of a schema matched a condition. * @@ -42,7 +40,23 @@ object SchemaPartition { * single payload field, the resulting schema would be a bijection from that payload field to the larger * datatype. */ - final case class TotalMatch[A](schema: Schema[A]) extends SchemaPartition[A] + final case class TotalMatch[A] private (schema: Schema[A]) + extends SchemaPartition[A] { + def withSchema(value: Schema[A]): TotalMatch[A] = { + copy(schema = value) + } + + } + object TotalMatch { + @scala.annotation.nowarn( + "msg=private method unapply in object TotalMatch is never used" + ) + private def unapply[A](c: TotalMatch[A]): Option[TotalMatch[A]] = Some(c) + def apply[A](schema: Schema[A]): TotalMatch[A] = { + new TotalMatch(schema) + } + + } /** * Indicates that only a subset of fields matched the partitioning condition. This datatype contains @@ -55,13 +69,37 @@ object SchemaPartition { * @param matching the partial schema resulting from the matching fields * @param notMatching the partial schema resulting from the non-matching fields */ - final case class SplittingMatch[A](matching: Schema[PartialData[A]], notMatching: Schema[PartialData[A]]) extends SchemaPartition[A] + // scalafmt: {maxColumn: 160} + final case class SplittingMatch[A] private (matching: Schema[PartialData[A]], notMatching: Schema[PartialData[A]]) extends SchemaPartition[A] { + def withMatching(value: Schema[PartialData[A]]): SplittingMatch[A] = { + copy(matching = value) + } + + def withNotMatching(value: Schema[PartialData[A]]): SplittingMatch[A] = { + copy(notMatching = value) + } + + } + object SplittingMatch { + @scala.annotation.nowarn("msg=private method unapply in object SplittingMatch is never used") + private def unapply[A](c: SplittingMatch[A]): Option[SplittingMatch[A]] = Some(c) + def apply[A](matching: Schema[PartialData[A]], notMatching: Schema[PartialData[A]]): SplittingMatch[A] = { + new SplittingMatch(matching, notMatching) + } + + } /** * Indicates that no field matched the condition. */ - final case class NoMatch[A]() extends SchemaPartition[A] - // format: on + final case class NoMatch[A] private () extends SchemaPartition[A] {} + object NoMatch { + @scala.annotation.nowarn("msg=private method unapply in object NoMatch is never used") + private def unapply[A](c: NoMatch[A]): Option[NoMatch[A]] = Some(c) + def apply[A](): NoMatch[A] = { + new NoMatch() + } + } private[schema] def apply( keep: Field[_, _] => Boolean, @@ -76,9 +114,8 @@ object SchemaPartition { fieldsAndIndexes: Vector[(Field[S, _], Int)] ): Schema[PartialData[S]] = { val indexes = fieldsAndIndexes.map(_._2) - val unsafeAccessFields = fieldsAndIndexes.map { - case (schemaField, _) => - toPartialDataField(schemaField) + val unsafeAccessFields = fieldsAndIndexes.map { case (schemaField, _) => + toPartialDataField(schemaField) } def const(values: IndexedSeq[Any]): PartialData[S] = PartialData.Partial(indexes, values, make) @@ -103,8 +140,8 @@ object SchemaPartition { SchemaPartition.NoMatch() } } else { - val partitioned = fields.zipWithIndex.partition { - case (schemaField, _) => keep(schemaField) + val partitioned = fields.zipWithIndex.partition { case (schemaField, _) => + keep(schemaField) } partitioned match { @@ -122,16 +159,16 @@ object SchemaPartition { } } - case BijectionSchema(underlying, bijection) => - apply(underlying) match { - case SchemaPartition.SplittingMatch(matching, notMatching) => + case bs: BijectionSchema[_, _] => + apply(bs.underlying) match { + case sm: SchemaPartition.SplittingMatch[_] => SchemaPartition.SplittingMatch( - matching.biject(_.map(bijection.to))(_.map(bijection.from)), - notMatching.biject(_.map(bijection.to))(_.map(bijection.from)) + sm.matching.biject(_.map(bs.bijection.to))(_.map(bs.bijection.from)), + sm.notMatching.biject(_.map(bs.bijection.to))(_.map(bs.bijection.from)) ) - case SchemaPartition.TotalMatch(total) => - SchemaPartition.TotalMatch(total.biject(bijection)) - case SchemaPartition.NoMatch() => SchemaPartition.NoMatch() + case tm: SchemaPartition.TotalMatch[_] => + SchemaPartition.TotalMatch(tm.schema.biject(bs.bijection)) + case _: SchemaPartition.NoMatch[_] => SchemaPartition.NoMatch() } case LazySchema(s) => apply(s.value) case _ => SchemaPartition.NoMatch() @@ -173,7 +210,7 @@ object SchemaPartition { } val from = (_: PartialData[S]) match { - case PartialData.Total(s) => field.get(s) + case t: PartialData.Total[_] => field.get(t.a) case _: PartialData.Partial[_] => // It's impossible to get the whole struct from a single field if it's not the only one codingError @@ -193,8 +230,8 @@ object SchemaPartition { field: Field[S, A] ): Field[PartialData[S], A] = { def access(product: PartialData[S]): A = product match { - case PartialData.Total(struct) => field.get(struct) - case PartialData.Partial(_, _, _) => codingError + case t: PartialData.Total[_] => field.get(t.a) + case _: PartialData.Partial[_] => codingError } Field(field.label, field.schema, access) } diff --git a/modules/core/src/smithy4s/schema/StreamingSchema.scala b/modules/core/src/smithy4s/schema/StreamingSchema.scala index 565496580..81051b391 100644 --- a/modules/core/src/smithy4s/schema/StreamingSchema.scala +++ b/modules/core/src/smithy4s/schema/StreamingSchema.scala @@ -16,4 +16,26 @@ package smithy4s.schema -case class StreamingSchema[A](fieldName: String, schema: Schema[A]) +case class StreamingSchema[A] private (fieldName: String, schema: Schema[A]) { + def withFieldName(value: String): StreamingSchema[A] = { + copy(fieldName = value) + } + + def withSchema(value: Schema[A]): StreamingSchema[A] = { + copy(schema = value) + } + +} + +object StreamingSchema { + @scala.annotation.nowarn( + "msg=private method unapply in object StreamingSchema is never used" + ) + private def unapply[A](c: StreamingSchema[A]): Option[StreamingSchema[A]] = + Some( + c + ) + def apply[A](fieldName: String, schema: Schema[A]): StreamingSchema[A] = { + new StreamingSchema(fieldName, schema) + } +} diff --git a/modules/decline/src/core/OptsVisitor.scala b/modules/decline/src/core/OptsVisitor.scala index 3735d9062..d3e39e5f5 100644 --- a/modules/decline/src/core/OptsVisitor.scala +++ b/modules/decline/src/core/OptsVisitor.scala @@ -93,12 +93,12 @@ object OptsVisitor extends SchemaVisitor[Opts] { self => val nameMap: Map[String, E] = values.map(v => v.stringValue -> v.value).toMap val extract: String => Option[E] = tag match { - case EnumTag.OpenIntEnum(unknown) => - _.toIntOption.map(i => ordinalMap.getOrElse(i, unknown(i))) + case oie: EnumTag.OpenIntEnum[_] => + _.toIntOption.map(i => ordinalMap.getOrElse(i, oie.unknown(i))) case EnumTag.ClosedIntEnum => _.toIntOption.flatMap(ordinalMap.get) - case EnumTag.OpenStringEnum(unknown) => - str => Some(nameMap.getOrElse(str, unknown(str))) + case ose: EnumTag.OpenStringEnum[_] => + str => Some(nameMap.getOrElse(str, ose.unknown(str))) case EnumTag.ClosedStringEnum => nameMap.get(_) } diff --git a/modules/dynamic/src/smithy4s/dynamic/internals/DynamicModelCompiler.scala b/modules/dynamic/src/smithy4s/dynamic/internals/DynamicModelCompiler.scala index 6fc107fb0..8a8b5f6fd 100644 --- a/modules/dynamic/src/smithy4s/dynamic/internals/DynamicModelCompiler.scala +++ b/modules/dynamic/src/smithy4s/dynamic/internals/DynamicModelCompiler.scala @@ -423,7 +423,7 @@ private[dynamic] object Compiler { val input = shape.input.map(_.target) val output = shape.output.map(_.target) - val errorId = id.copy(name = id.name + "Error") + val errorId = id.withName(id.name + "Error") val allOperationErrors = (serviceErrors ++ shape.errors).toNel val errorUnionLazy = allOperationErrors.traverse { err => diff --git a/modules/http4s-kernel/src/smithy4s/http4s/kernel/package.scala b/modules/http4s-kernel/src/smithy4s/http4s/kernel/package.scala index b9f81ff75..fd9390da5 100644 --- a/modules/http4s-kernel/src/smithy4s/http4s/kernel/package.scala +++ b/modules/http4s-kernel/src/smithy4s/http4s/kernel/package.scala @@ -131,8 +131,8 @@ package object kernel { case Smithy4sHttpMethod.DELETE => Method.DELETE case Smithy4sHttpMethod.GET => Method.GET case Smithy4sHttpMethod.PATCH => Method.PATCH - case Smithy4sHttpMethod.OTHER(v) => - Method.fromString(v) match { + case o: Smithy4sHttpMethod.OTHER => + Method.fromString(o.value) match { case Left(e) => throw e case Right(m) => m } @@ -147,8 +147,8 @@ package object kernel { case Smithy4sHttpMethod.DELETE => Some(Method.DELETE) case Smithy4sHttpMethod.GET => Some(Method.GET) case Smithy4sHttpMethod.PATCH => Some(Method.PATCH) - case Smithy4sHttpMethod.OTHER(v) => - Method.fromString(v) match { + case o: Smithy4sHttpMethod.OTHER => + Method.fromString(o.value) match { case Left(_) => None case Right(m) => Some(m) } diff --git a/modules/json/src/smithy4s/json/internals/Cursor.scala b/modules/json/src/smithy4s/json/internals/Cursor.scala index 421daefd6..9ce459eb6 100644 --- a/modules/json/src/smithy4s/json/internals/Cursor.scala +++ b/modules/json/src/smithy4s/json/internals/Cursor.scala @@ -73,14 +73,14 @@ private[internals] class Cursor private[internals] () { } def payloadError[A](codec: JCodec[A], message: String): Nothing = - throw new PayloadError(getPath(Nil), codec.expecting, message) + throw PayloadError(getPath(Nil), codec.expecting, message) def requiredFieldError[A](codec: JCodec[A], field: String): Nothing = requiredFieldError(codec.expecting, field) def requiredFieldError[A](expecting: String, field: String): Nothing = { - val path = getPath(new PayloadPath.Segment.Label(field) :: Nil) - throw new PayloadError(path, expecting, "Missing required field") + val path = getPath(PayloadPath.Segment.Label(field) :: Nil) + throw PayloadError(path, expecting, "Missing required field") } private[internals] def getPath( @@ -92,11 +92,11 @@ private[internals] class Cursor private[internals] () { top -= 1 val label = labelStack(top) val segment = - if (label ne null) new PayloadPath.Segment.Label(label) - else new PayloadPath.Segment.Index(indexStack(top)) + if (label ne null) PayloadPath.Segment.Label(label) + else PayloadPath.Segment.Index(indexStack(top)) list = segment :: list } - new PayloadPath(list) + PayloadPath.fromSegments(list) } private def getExpected(): String = @@ -123,5 +123,5 @@ object Cursor { } private[this] def payloadError(cursor: Cursor, message: String): Nothing = - throw new PayloadError(cursor.getPath(Nil), cursor.getExpected(), message) + throw PayloadError(cursor.getPath(Nil), cursor.getExpected(), message) } diff --git a/modules/json/src/smithy4s/json/internals/SchemaVisitorJCodec.scala b/modules/json/src/smithy4s/json/internals/SchemaVisitorJCodec.scala index 5e6c1924e..7ec2be964 100644 --- a/modules/json/src/smithy4s/json/internals/SchemaVisitorJCodec.scala +++ b/modules/json/src/smithy4s/json/internals/SchemaVisitorJCodec.scala @@ -1175,8 +1175,8 @@ private[smithy4s] class SchemaVisitorJCodec( s"enumeration: [${values.map(_.stringValue).mkString(", ")}]" private val decode: (JsonReader, String) => E = tag match { - case EnumTag.OpenStringEnum(unknown) => - (_, str) => fromNameOpen(str, unknown) + case ose: EnumTag.OpenStringEnum[_] => + (_, str) => fromNameOpen(str, ose.unknown) case _ => (in, str) => fromName(str) match { @@ -1222,8 +1222,8 @@ private[smithy4s] class SchemaVisitorJCodec( s"enumeration: [${values.map(_.stringValue).mkString(", ")}]" private val decode: (JsonReader, Int) => E = tag match { - case EnumTag.OpenIntEnum(unknown) => - (_, i) => fromOrdinalOpen(i, unknown) + case oie: EnumTag.OpenIntEnum[_] => + (_, i) => fromOrdinalOpen(i, oie.unknown) case _ => (in, i) => fromOrdinal(i) match { diff --git a/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecPropertyTests.scala b/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecPropertyTests.scala index 0ada10d30..1ba051065 100644 --- a/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecPropertyTests.scala +++ b/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecPropertyTests.scala @@ -103,14 +103,14 @@ class SchemaVisitorJsonCodecPropertyTests() expect(min.forall(_ <= value)) expect(max.forall(_ >= value)) } - case Left(PayloadError(_, _, message)) => + case Left(pe: PayloadError) => hint.value match { case Length(min, max) => - expect(min.isEmpty || message.contains(min.get.toString)) - expect(max.isEmpty || message.contains(max.get.toString)) + expect(min.isEmpty || pe.message.contains(min.get.toString)) + expect(max.isEmpty || pe.message.contains(max.get.toString)) case Range(min, max) => - expect(min.isEmpty || message.contains(min.get.toString)) - expect(max.isEmpty || message.contains(max.get.toString)) + expect(min.isEmpty || pe.message.contains(min.get.toString)) + expect(max.isEmpty || pe.message.contains(max.get.toString)) } case _ => fail("result should have matched one of the above cases") } @@ -155,10 +155,16 @@ class SchemaVisitorJsonCodecPropertyTests() ).asInstanceOf[Vector[Schema[DynData]]] ) hint match { - case Hints.Binding.StaticBinding(_, l: Length) => lengthGen(l) - case Hints.Binding.StaticBinding(_, r: Range) => rangeGen(r) + case staticBindingValue(l: Length) => lengthGen(l) + case staticBindingValue(r: Range) => rangeGen(r) case _ => Gen.const(int.asInstanceOf[Schema[Any]]) } } + object staticBindingValue { + def unapply[A](sb: Hints.Binding.StaticBinding[A]): Option[A] = { + Some(sb.value) + } + } + } diff --git a/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecTests.scala b/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecTests.scala index 7a62dddf3..c97cb248a 100644 --- a/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecTests.scala +++ b/modules/json/test/src/smithy4s/json/SchemaVisitorJCodecTests.scala @@ -167,9 +167,9 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Foo](json) fail("Unexpected success") } catch { - case PayloadError(path, expected, _) => - expect.same(path, PayloadPath("b")) - expect.same(expected, "JsNull or int") + case pe: PayloadError => + expect.same(pe.path, PayloadPath("b")) + expect.same(pe.expected, "JsNull or int") } } @@ -188,10 +188,10 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Bar](json) fail("Unexpected success") } catch { - case ex @ PayloadError(path, expected, _) => - expect(path == PayloadPath(jsonNameValue)) - expect(expected == jsonNameValue) - expect.same(ex.getMessage(), "Missing required field (path: .oldName)") + case pe: PayloadError => + expect(pe.path == PayloadPath(jsonNameValue)) + expect(pe.expected == jsonNameValue) + expect.same(pe.getMessage(), "Missing required field (path: .oldName)") } } @@ -295,10 +295,10 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Either[Int, String]](json) fail("Unexpected success") } catch { - case PayloadError(path, expected, msg) => - expect.same(path, PayloadPath("left")) - expect.same(expected, "int") - expect(msg.contains("illegal number")) + case pe: PayloadError => + expect.same(pe.path, PayloadPath("left")) + expect.same(pe.expected, "int") + expect(pe.message.contains("illegal number")) } } @@ -309,10 +309,10 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Either[Int, String]](json) fail("Unexpected success") } catch { - case PayloadError(path, expected, msg) => - expect.same(path, PayloadPath.root) - expect.same(expected, "tagged-union") - expect(msg.contains("Expected JSON object")) + case pe: PayloadError => + expect.same(pe.path, PayloadPath.root) + expect.same(pe.expected, "tagged-union") + expect(pe.message.contains("Expected JSON object")) } } @@ -524,9 +524,9 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Foo3](json) fail("Unexpected success") } catch { - case PayloadError(path, _, message) => - expect.same(message, "length required to be <= 10, but was 11") - expect.same(path, PayloadPath.parse("bar.0.str")) + case pe: PayloadError => + expect.same(pe.message, "length required to be <= 10, but was 11") + expect.same(pe.path, PayloadPath.parse("bar.0.str")) } } @@ -541,8 +541,8 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Map[String, Int]](items) fail("Unexpected success") } catch { - case PayloadError(_, _, message) => - expect(message == "Input map exceeded max arity of 1024") + case pe: PayloadError => + expect(pe.message == "Input map exceeded max arity of 1024") } } @@ -556,8 +556,8 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[List[Int]](items) fail("Unexpected success") } catch { - case PayloadError(_, _, message) => - expect.same(message, "Input list exceeded max arity of 1024") + case pe: PayloadError => + expect.same(pe.message, "Input list exceeded max arity of 1024") } } @@ -567,8 +567,11 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Document](items) fail("Unexpected success") } catch { - case PayloadError(_, _, message) => - expect.same(message, "Input JSON document exceeded max arity of 1024") + case pe: PayloadError => + expect.same( + pe.message, + "Input JSON document exceeded max arity of 1024" + ) } } @@ -579,8 +582,11 @@ class SchemaVisitorJCodecTests() extends FunSuite { val _ = readFromString[Document](items) fail("Unexpected success") } catch { - case PayloadError(_, _, message) => - expect.same(message, "Input JSON document exceeded max arity of 1024") + case pe: PayloadError => + expect.same( + pe.message, + "Input JSON document exceeded max arity of 1024" + ) } } diff --git a/modules/json/test/src/smithy4s/json/internals/CursorSpec.scala b/modules/json/test/src/smithy4s/json/internals/CursorSpec.scala index b95a1c8ca..f0fb8ddb8 100644 --- a/modules/json/test/src/smithy4s/json/internals/CursorSpec.scala +++ b/modules/json/test/src/smithy4s/json/internals/CursorSpec.scala @@ -28,7 +28,7 @@ final class CursorSpec extends FunSuite { c.push(1) assertEquals( c.getPath(List.empty), - PayloadPath(List(PayloadPath.Segment(1))) + PayloadPath.fromSegments(List(PayloadPath.Segment(1))) ) } diff --git a/modules/tests/src/smithy4s/tests/PizzaSpec.scala b/modules/tests/src/smithy4s/tests/PizzaSpec.scala index 634b6b25f..4e8906e0f 100644 --- a/modules/tests/src/smithy4s/tests/PizzaSpec.scala +++ b/modules/tests/src/smithy4s/tests/PizzaSpec.scala @@ -400,7 +400,7 @@ abstract class PizzaSpec res <- runServer( impl, { - case HttpPayloadError(smithy4s.codecs.PayloadPath(List()), _, _) => + case hpe: HttpPayloadError if hpe.path.segments.isEmpty => smithy4s.example.GenericClientError("Oops") case PizzaAdminServiceImpl.Boom => smithy4s.example.GenericServerError("Crash") diff --git a/modules/xml/src/smithy4s/xml/XPath.scala b/modules/xml/src/smithy4s/xml/XPath.scala index 60583ca8c..760337744 100644 --- a/modules/xml/src/smithy4s/xml/XPath.scala +++ b/modules/xml/src/smithy4s/xml/XPath.scala @@ -28,7 +28,7 @@ import smithy4s.codecs.PayloadPath */ case class XPath(reversedSegments: List[XPath.Segment]) { def render: String = reversedSegments.reverse.map(_.render).mkString(".") - def toPayloadPath: PayloadPath = PayloadPath { + def toPayloadPath: PayloadPath = PayloadPath.fromSegments { reversedSegments.reverse.map { xpathSegment => xpathSegment match { case Index(index) => PayloadPath.Segment(index) diff --git a/modules/xml/src/smithy4s/xml/internals/XmlDecoderSchemaVisitor.scala b/modules/xml/src/smithy4s/xml/internals/XmlDecoderSchemaVisitor.scala index db9d1fff5..9a56ad673 100644 --- a/modules/xml/src/smithy4s/xml/internals/XmlDecoderSchemaVisitor.scala +++ b/modules/xml/src/smithy4s/xml/internals/XmlDecoderSchemaVisitor.scala @@ -112,8 +112,8 @@ private[smithy4s] class XmlDecoderSchemaVisitor( val valueMap = values.map(ev => ev.intValue -> ev.value).toMap val handler: String => Option[E] = tag match { - case EnumTag.OpenIntEnum(unknown) => - _.toIntOption.map(i => valueMap.getOrElse(i, unknown(i))) + case oie: EnumTag.OpenIntEnum[_] => + _.toIntOption.map(i => valueMap.getOrElse(i, oie.unknown(i))) case _ => _.toIntOption.flatMap(valueMap.get) } @@ -125,8 +125,8 @@ private[smithy4s] class XmlDecoderSchemaVisitor( val valueMap = values.map(ev => ev.stringValue -> ev.value).toMap val handler: String => Option[E] = tag match { - case EnumTag.OpenStringEnum(unknown) => - s => Some(valueMap.getOrElse(s, unknown(s))) + case ose: EnumTag.OpenStringEnum[_] => + s => Some(valueMap.getOrElse(s, ose.unknown(s))) case _ => valueMap.get(_) }