Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

case class rewrite for core #1391

Draft
wants to merge 6 commits into
base: series/0.19
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion modules/core/src-jvm-native/smithy4s/Timestamp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
19 changes: 18 additions & 1 deletion modules/core/src/smithy4s/ConstraintError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
52 changes: 44 additions & 8 deletions modules/core/src/smithy4s/Hints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)

Expand Down Expand Up @@ -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]
Expand Down
74 changes: 53 additions & 21 deletions modules/core/src/smithy4s/PartialData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,72 @@ 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)
* into a single piece of data. It is the responsibility of the caller to ensure that
* 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))
}
}
}
23 changes: 23 additions & 0 deletions modules/core/src/smithy4s/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand All @@ -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] = {
Expand Down
Loading