diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionPattern.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionPattern.scala new file mode 100644 index 000000000..05ba0179e --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionPattern.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.internals.StructurePatternRefinementProvider._ +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object NestedInnerUnionPattern extends Newtype[NestedInnerUnionTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedInnerUnionPattern") + val hints: Hints = Hints( + Hints.dynamic(ShapeId("alloy", "structurePattern"), smithy4s.Document.obj("pattern" -> smithy4s.Document.fromString("{label}:{value}"), "target" -> smithy4s.Document.fromString("smithy4s.example#NestedInnerUnionTarget"))), + ) + val underlyingSchema: Schema[NestedInnerUnionTarget] = string.refined[NestedInnerUnionTarget](alloy.StructurePattern(pattern = "{label}:{value}", target = smithy4s.ShapeId(namespace = "smithy4s.example", name = "NestedInnerUnionTarget"))).withId(id).addHints(hints) + implicit val schema: Schema[NestedInnerUnionPattern] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionTarget.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionTarget.scala new file mode 100644 index 000000000..5ddf110f3 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedInnerUnionTarget.scala @@ -0,0 +1,68 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.int +import smithy4s.schema.Schema.string +import smithy4s.schema.Schema.union + +sealed trait NestedInnerUnionTarget extends scala.Product with scala.Serializable { self => + @inline final def widen: NestedInnerUnionTarget = this + def $ordinal: Int + + object project { + def str: Option[String] = NestedInnerUnionTarget.StrCase.alt.project.lift(self).map(_.str) + def num: Option[Int] = NestedInnerUnionTarget.NumCase.alt.project.lift(self).map(_.num) + } + + def accept[A](visitor: NestedInnerUnionTarget.Visitor[A]): A = this match { + case value: NestedInnerUnionTarget.StrCase => visitor.str(value.str) + case value: NestedInnerUnionTarget.NumCase => visitor.num(value.num) + } +} +object NestedInnerUnionTarget extends ShapeTag.Companion[NestedInnerUnionTarget] { + + def str(str: String): NestedInnerUnionTarget = StrCase(str) + def num(num: Int): NestedInnerUnionTarget = NumCase(num) + + val id: ShapeId = ShapeId("smithy4s.example", "NestedInnerUnionTarget") + + val hints: Hints = Hints.empty + + final case class StrCase(str: String) extends NestedInnerUnionTarget { final def $ordinal: Int = 0 } + final case class NumCase(num: Int) extends NestedInnerUnionTarget { final def $ordinal: Int = 1 } + + object StrCase { + val hints: Hints = Hints.empty + val schema: Schema[NestedInnerUnionTarget.StrCase] = bijection(string.addHints(hints), NestedInnerUnionTarget.StrCase(_), _.str) + val alt = schema.oneOf[NestedInnerUnionTarget]("str") + } + object NumCase { + val hints: Hints = Hints.empty + val schema: Schema[NestedInnerUnionTarget.NumCase] = bijection(int.addHints(hints), NestedInnerUnionTarget.NumCase(_), _.num) + val alt = schema.oneOf[NestedInnerUnionTarget]("num") + } + + trait Visitor[A] { + def str(value: String): A + def num(value: Int): A + } + + object Visitor { + trait Default[A] extends Visitor[A] { + def default: A + def str(value: String): A = default + def num(value: Int): A = default + } + } + + implicit val schema: Schema[NestedInnerUnionTarget] = union[NestedInnerUnionTarget]( + NestedInnerUnionTarget.StrCase.alt, + NestedInnerUnionTarget.NumCase.alt, + ){ + _.$ordinal + }.withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePattern.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePattern.scala new file mode 100644 index 000000000..f2bd3211f --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePattern.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.internals.StructurePatternRefinementProvider._ +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object NestedMiddlePattern extends Newtype[NestedMiddlePatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedMiddlePattern") + val hints: Hints = Hints( + Hints.dynamic(ShapeId("alloy", "structurePattern"), smithy4s.Document.obj("pattern" -> smithy4s.Document.fromString("{id}|{choice}"), "target" -> smithy4s.Document.fromString("smithy4s.example#NestedMiddlePatternTarget"))), + ) + val underlyingSchema: Schema[NestedMiddlePatternTarget] = string.refined[NestedMiddlePatternTarget](alloy.StructurePattern(pattern = "{id}|{choice}", target = smithy4s.ShapeId(namespace = "smithy4s.example", name = "NestedMiddlePatternTarget"))).withId(id).addHints(hints) + implicit val schema: Schema[NestedMiddlePattern] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePatternTarget.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePatternTarget.scala new file mode 100644 index 000000000..3aa4e2dbf --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedMiddlePatternTarget.scala @@ -0,0 +1,24 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.int +import smithy4s.schema.Schema.struct + +final case class NestedMiddlePatternTarget(id: Int, choice: NestedInnerUnionPattern) + +object NestedMiddlePatternTarget extends ShapeTag.Companion[NestedMiddlePatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedMiddlePatternTarget") + + val hints: Hints = Hints.empty + + // constructor using the original order from the spec + private def make(id: Int, choice: NestedInnerUnionPattern): NestedMiddlePatternTarget = NestedMiddlePatternTarget(id, choice) + + implicit val schema: Schema[NestedMiddlePatternTarget] = struct[NestedMiddlePatternTarget]( + int.required[NestedMiddlePatternTarget]("id", _.id), + NestedInnerUnionPattern.schema.required[NestedMiddlePatternTarget]("choice", _.choice), + )(make).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePattern.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePattern.scala new file mode 100644 index 000000000..a3662fdec --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePattern.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.internals.StructurePatternRefinementProvider._ +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object NestedStructurePattern extends Newtype[NestedStructurePatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedStructurePattern") + val hints: Hints = Hints( + Hints.dynamic(ShapeId("alloy", "structurePattern"), smithy4s.Document.obj("pattern" -> smithy4s.Document.fromString("{name}/{inner}"), "target" -> smithy4s.Document.fromString("smithy4s.example#NestedStructurePatternTarget"))), + ) + val underlyingSchema: Schema[NestedStructurePatternTarget] = string.refined[NestedStructurePatternTarget](alloy.StructurePattern(pattern = "{name}/{inner}", target = smithy4s.ShapeId(namespace = "smithy4s.example", name = "NestedStructurePatternTarget"))).withId(id).addHints(hints) + implicit val schema: Schema[NestedStructurePattern] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePatternTarget.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePatternTarget.scala new file mode 100644 index 000000000..8acd82c0c --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedStructurePatternTarget.scala @@ -0,0 +1,24 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.string +import smithy4s.schema.Schema.struct + +final case class NestedStructurePatternTarget(name: String, inner: TestStructurePattern) + +object NestedStructurePatternTarget extends ShapeTag.Companion[NestedStructurePatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedStructurePatternTarget") + + val hints: Hints = Hints.empty + + // constructor using the original order from the spec + private def make(name: String, inner: TestStructurePattern): NestedStructurePatternTarget = NestedStructurePatternTarget(name, inner) + + implicit val schema: Schema[NestedStructurePatternTarget] = struct[NestedStructurePatternTarget]( + string.required[NestedStructurePatternTarget]("name", _.name), + TestStructurePattern.schema.required[NestedStructurePatternTarget]("inner", _.inner), + )(make).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPattern.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPattern.scala new file mode 100644 index 000000000..53464c775 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPattern.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.internals.StructurePatternRefinementProvider._ +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object NestedTopPattern extends Newtype[NestedTopPatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedTopPattern") + val hints: Hints = Hints( + Hints.dynamic(ShapeId("alloy", "structurePattern"), smithy4s.Document.obj("pattern" -> smithy4s.Document.fromString("{tenant}/{resource}"), "target" -> smithy4s.Document.fromString("smithy4s.example#NestedTopPatternTarget"))), + ) + val underlyingSchema: Schema[NestedTopPatternTarget] = string.refined[NestedTopPatternTarget](alloy.StructurePattern(pattern = "{tenant}/{resource}", target = smithy4s.ShapeId(namespace = "smithy4s.example", name = "NestedTopPatternTarget"))).withId(id).addHints(hints) + implicit val schema: Schema[NestedTopPattern] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPatternTarget.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPatternTarget.scala new file mode 100644 index 000000000..35f13a836 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedTopPatternTarget.scala @@ -0,0 +1,24 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.string +import smithy4s.schema.Schema.struct + +final case class NestedTopPatternTarget(tenant: String, resource: NestedMiddlePattern) + +object NestedTopPatternTarget extends ShapeTag.Companion[NestedTopPatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedTopPatternTarget") + + val hints: Hints = Hints.empty + + // constructor using the original order from the spec + private def make(tenant: String, resource: NestedMiddlePattern): NestedTopPatternTarget = NestedTopPatternTarget(tenant, resource) + + implicit val schema: Schema[NestedTopPatternTarget] = struct[NestedTopPatternTarget]( + string.required[NestedTopPatternTarget]("tenant", _.tenant), + NestedMiddlePattern.schema.required[NestedTopPatternTarget]("resource", _.resource), + )(make).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPattern.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPattern.scala new file mode 100644 index 000000000..fb2b095cc --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPattern.scala @@ -0,0 +1,18 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.internals.StructurePatternRefinementProvider._ +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object NestedUnionPattern extends Newtype[NestedUnionPatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedUnionPattern") + val hints: Hints = Hints( + Hints.dynamic(ShapeId("alloy", "structurePattern"), smithy4s.Document.obj("pattern" -> smithy4s.Document.fromString("{prefix}/{tagged}"), "target" -> smithy4s.Document.fromString("smithy4s.example#NestedUnionPatternTarget"))), + ) + val underlyingSchema: Schema[NestedUnionPatternTarget] = string.refined[NestedUnionPatternTarget](alloy.StructurePattern(pattern = "{prefix}/{tagged}", target = smithy4s.ShapeId(namespace = "smithy4s.example", name = "NestedUnionPatternTarget"))).withId(id).addHints(hints) + implicit val schema: Schema[NestedUnionPattern] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPatternTarget.scala b/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPatternTarget.scala new file mode 100644 index 000000000..2fc820990 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/NestedUnionPatternTarget.scala @@ -0,0 +1,24 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.string +import smithy4s.schema.Schema.struct + +final case class NestedUnionPatternTarget(prefix: String, tagged: TestUnionPattern) + +object NestedUnionPatternTarget extends ShapeTag.Companion[NestedUnionPatternTarget] { + val id: ShapeId = ShapeId("smithy4s.example", "NestedUnionPatternTarget") + + val hints: Hints = Hints.empty + + // constructor using the original order from the spec + private def make(prefix: String, tagged: TestUnionPattern): NestedUnionPatternTarget = NestedUnionPatternTarget(prefix, tagged) + + implicit val schema: Schema[NestedUnionPatternTarget] = struct[NestedUnionPatternTarget]( + string.required[NestedUnionPatternTarget]("prefix", _.prefix), + TestUnionPattern.schema.required[NestedUnionPatternTarget]("tagged", _.tagged), + )(make).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/package.scala b/modules/bootstrapped/src/generated/smithy4s/example/package.scala index d49edc153..96315bf9a 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/package.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/package.scala @@ -86,6 +86,11 @@ package object example { type MyLocalTime = smithy4s.example.MyLocalTime.Type type MyOffsetDateTime = smithy4s.example.MyOffsetDateTime.Type type Name = smithy4s.example.Name.Type + type NestedInnerUnionPattern = smithy4s.example.NestedInnerUnionPattern.Type + type NestedMiddlePattern = smithy4s.example.NestedMiddlePattern.Type + type NestedStructurePattern = smithy4s.example.NestedStructurePattern.Type + type NestedTopPattern = smithy4s.example.NestedTopPattern.Type + type NestedUnionPattern = smithy4s.example.NestedUnionPattern.Type type NonEmptyCandies = smithy4s.example.NonEmptyCandies.Type type NonEmptyMapNumbers = smithy4s.example.NonEmptyMapNumbers.Type type NonEmptyNames = smithy4s.example.NonEmptyNames.Type diff --git a/modules/bootstrapped/test/src/smithy4s/internals/StructurePatternRefinementProviderSpec.scala b/modules/bootstrapped/test/src/smithy4s/internals/StructurePatternRefinementProviderSpec.scala index bfea9f46e..a100dcd5d 100644 --- a/modules/bootstrapped/test/src/smithy4s/internals/StructurePatternRefinementProviderSpec.scala +++ b/modules/bootstrapped/test/src/smithy4s/internals/StructurePatternRefinementProviderSpec.scala @@ -197,6 +197,117 @@ final class StructurePatternRefinementProviderSpec extends FunSuite { runDecode("{label}:{value}", "one:", one, shouldFail = true) } + // Nested structure patterns using generated types from structure_pattern.smithy + + test("nested structure pattern - encoding (structure in structure)") { + import smithy4s.example._ + val inner = TestStructurePattern(TestStructurePatternTarget("foo", 42)) + val target = NestedStructurePatternTarget("hello", inner) + runEncode("{name}/{inner}", target, "hello/foo-42") + } + + test("nested structure pattern - decoding (structure in structure)") { + import smithy4s.example._ + val inner = TestStructurePattern(TestStructurePatternTarget("foo", 42)) + val target = NestedStructurePatternTarget("hello", inner) + runDecode("{name}/{inner}", "hello/foo-42", target) + runDecode("{name}/{inner}", "hello/foo-", target, shouldFail = true) + runDecode("{name}/{inner}", "hello/", target, shouldFail = true) + } + + test("nested structure pattern - encoding (union in structure)") { + import smithy4s.example._ + val strVal = NestedUnionPatternTarget( + "abc", + TestUnionPattern(TestUnionPatternTarget.one("hi")) + ) + val numVal = NestedUnionPatternTarget( + "abc", + TestUnionPattern(TestUnionPatternTarget.two(99)) + ) + runEncode("{prefix}/{tagged}", strVal, "abc/one:hi") + runEncode("{prefix}/{tagged}", numVal, "abc/two:99") + } + + test("nested structure pattern - decoding (union in structure)") { + import smithy4s.example._ + val strVal = NestedUnionPatternTarget( + "abc", + TestUnionPattern(TestUnionPatternTarget.one("hi")) + ) + val numVal = NestedUnionPatternTarget( + "abc", + TestUnionPattern(TestUnionPatternTarget.two(99)) + ) + runDecode("{prefix}/{tagged}", "abc/one:hi", strVal) + runDecode("{prefix}/{tagged}", "abc/two:99", numVal) + runDecode("{prefix}/{tagged}", "abc/unknown:x", strVal, shouldFail = true) + runDecode("{prefix}/{tagged}", "abc/one:", strVal, shouldFail = true) + } + + test( + "nested structure pattern - encoding (structure wrapping structure wrapping union)" + ) { + import smithy4s.example._ + val value = NestedTopPatternTarget( + "acme", + NestedMiddlePattern( + NestedMiddlePatternTarget( + 7, + NestedInnerUnionPattern(NestedInnerUnionTarget.str("hello")) + ) + ) + ) + runEncode("{tenant}/{resource}", value, "acme/7|str:hello") + + val value2 = NestedTopPatternTarget( + "acme", + NestedMiddlePattern( + NestedMiddlePatternTarget( + 7, + NestedInnerUnionPattern(NestedInnerUnionTarget.num(42)) + ) + ) + ) + runEncode("{tenant}/{resource}", value2, "acme/7|num:42") + } + + test( + "nested structure pattern - decoding (structure wrapping structure wrapping union)" + ) { + import smithy4s.example._ + val value = NestedTopPatternTarget( + "acme", + NestedMiddlePattern( + NestedMiddlePatternTarget( + 7, + NestedInnerUnionPattern(NestedInnerUnionTarget.str("hello")) + ) + ) + ) + runDecode("{tenant}/{resource}", "acme/7|str:hello", value) + + val value2 = NestedTopPatternTarget( + "acme", + NestedMiddlePattern( + NestedMiddlePatternTarget( + 7, + NestedInnerUnionPattern(NestedInnerUnionTarget.num(42)) + ) + ) + ) + runDecode("{tenant}/{resource}", "acme/7|num:42", value2) + + runDecode( + "{tenant}/{resource}", + "acme/7|unknown:x", + value, + shouldFail = true + ) + runDecode("{tenant}/{resource}", "acme/7|str:", value, shouldFail = true) + runDecode("{tenant}/{resource}", "acme/", value, shouldFail = true) + } + private def runEncode[A](pattern: String, input: A, expect: String)(implicit sch: Schema[A], loc: Location diff --git a/sampleSpecs/structure_pattern.smithy b/sampleSpecs/structure_pattern.smithy index a7dd3098a..4b2bcbf28 100644 --- a/sampleSpecs/structure_pattern.smithy +++ b/sampleSpecs/structure_pattern.smithy @@ -22,3 +22,55 @@ union TestUnionPatternTarget { one: String two: Integer } + +@structurePattern(pattern: "{name}/{inner}", target: NestedStructurePatternTarget) +string NestedStructurePattern + +structure NestedStructurePatternTarget { + @required + name: String + + @required + inner: TestStructurePattern +} + +@structurePattern(pattern: "{prefix}/{tagged}", target: NestedUnionPatternTarget) +string NestedUnionPattern + +structure NestedUnionPatternTarget { + @required + prefix: String + + @required + tagged: TestUnionPattern +} + +@structurePattern(pattern: "{label}:{value}", target: NestedInnerUnionTarget) +string NestedInnerUnionPattern + +union NestedInnerUnionTarget { + str: String + num: Integer +} + +@structurePattern(pattern: "{id}|{choice}", target: NestedMiddlePatternTarget) +string NestedMiddlePattern + +structure NestedMiddlePatternTarget { + @required + id: Integer + + @required + choice: NestedInnerUnionPattern +} + +@structurePattern(pattern: "{tenant}/{resource}", target: NestedTopPatternTarget) +string NestedTopPattern + +structure NestedTopPatternTarget { + @required + tenant: String + + @required + resource: NestedMiddlePattern +}