diff --git a/CHANGELOG.md b/CHANGELOG.md index 47303d0a4..e4ba2bf82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 0.18.14 +* Add support for decoding Document representations of untagged unions * Update aws-http4s clients using json to have a maxArity of Int.MaxValue # 0.18.13 diff --git a/modules/bootstrapped/test/src/smithy4s/DocumentSpec.scala b/modules/bootstrapped/test/src/smithy4s/DocumentSpec.scala index 17fdcaddb..0c9f549bb 100644 --- a/modules/bootstrapped/test/src/smithy4s/DocumentSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/DocumentSpec.scala @@ -22,6 +22,7 @@ import smithy4s.example.IntList import alloy.Discriminated import munit._ import smithy4s.example.OperationOutput +import alloy.Untagged class DocumentSpec() extends FunSuite { @@ -138,6 +139,30 @@ class DocumentSpec() extends FunSuite { expect.same(roundTripped, Right(fooOrBar)) } + test("untagged unions encoding") { + implicit val eitherSchema: Schema[Either[Long, String]] = { + val left = Schema.long + val right = Schema.string + + Schema + .either(left, right) + .addHints( + Untagged() + ) + } + val longOrString: Either[Long, String] = Right("hello") + + val document = Document.encode(longOrString) + import Document._ + val expectedDocument = + Document.fromString("hello") + + val roundTripped = Document.decode[Either[Long, String]](document) + + expect.same(document, expectedDocument) + expect.same(roundTripped, Right(longOrString)) + } + test("discriminated unions encoding - empty structure alternative") { val fooOrBaz: Either[Foo, Baz] = Right(Baz()) diff --git a/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala b/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala index abaa7e9b7..0f4fcb1dd 100644 --- a/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala +++ b/modules/core/src/smithy4s/internals/DocumentDecoderSchemaVisitor.scala @@ -33,6 +33,7 @@ import smithy4s.schema._ import java.util.Base64 import java.util.UUID import scala.collection.immutable.ListMap +import alloy.Untagged trait DocumentDecoder[A] { self => def apply(history: List[PayloadPath.Segment], document: Document): A @@ -445,6 +446,34 @@ class DocumentDecoderSchemaVisitor( } + private def untaggedUnion[S]( + decoders: DecoderMap[S] + ): DocumentDecoder[S] = { + val decodersList = decoders.values.toList + val len = decodersList.length + + handleUnion { (pp: List[PayloadPath.Segment], document: Document) => + var z: S = null.asInstanceOf[S] + var i = 0 + while (z == null && i < len) { + try { + z = decodersList(i)(pp, document) + } catch { + case _: Throwable => i += 1 + } + } + if (z == null) { + throw new PayloadError( + PayloadPath(pp.reverse), + "Union", + "No valid alternatives found for untagged union" + ) + } else { + z + } + } + } + override def union[U]( shapeId: ShapeId, hints: Hints, @@ -466,6 +495,8 @@ class DocumentDecoderSchemaVisitor( hints match { case Discriminated.hint(discriminated) => discriminatedUnion(discriminated, decoders) + case Untagged.hint(_) => + untaggedUnion(decoders) case _ => taggedUnion(decoders) }