Skip to content

Commit 0d807bc

Browse files
lewisjklyisraelUkubukoz
authored
Sync series/0.18 into series/0.19 - Oct2025 (#1854)
* add in scripted test for refinement usage * add headers * Update test file to include run command * Update alloy-core, alloy-openapi, ... to 0.3.33 * Avoid throwing MatchError when surfacing unknown errors (#1846) * remove usage of alloy to test refinements * config-based dynamic hint enable (#1848) * config-based dynamic hint enable * update changelog * use namespace patterns --------- Co-authored-by: yisraelu <[email protected]> Co-authored-by: Scala Steward <[email protected]>
1 parent 68b2607 commit 0d807bc

File tree

13 files changed

+177
-16
lines changed

13 files changed

+177
-16
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ The behavior of `@default(null)` has changed to better align with Smithy semanti
6969

7070
## `Bijection` does no longer extends `Function` in [#1794](https://github.com/disneystreaming/smithy4s/pull/1794)
7171
Prevents using it as an implicit conversion in Scala 2
72+
73+
# 0.18.44
74+
75+
* Avoid an issue in which `SurfaceError` transformations would throw a `MatchError` upon being called for an operation that doesn't declare errors in [#1846](https://github.com/disneystreaming/smithy4s/pull/1846).
76+
* Add option to configure Dynamic Hint Bindings via Smithy metadata in [#1848](https://github.com/disneystreaming/smithy4s/pull/1848)
77+
7278
# 0.18.43
7379

7480
* Add support for generating dynamic hint bindings in [#1816](https://github.com/disneystreaming/smithy4s/pull/1816)

modules/bootstrapped/src/generated/smithy4s/example/ShouldHaveDynamicBinding.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ object ShouldHaveDynamicBinding extends ShapeTag.Companion[ShouldHaveDynamicBind
1515
val hints: Hints = Hints(
1616
smithy.api.Since("1"),
1717
Hints.dynamic(ShapeId("smithy4s.example", "testDynamicBinding"), smithy4s.Document.obj("str" -> smithy4s.Document.fromString("test"))),
18+
Hints.dynamic(ShapeId("smithy4s.example.dynamic_traits", "thisWillBeDynamic"), smithy4s.Document.obj("test" -> smithy4s.Document.fromDouble(101.0d))),
1819
).lazily
1920

2021
// constructor using the original order from the spec

modules/bootstrapped/test/src/smithy4s/TransformationSpec.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package smithy4s
1818

1919
import smithy4s.example._
20+
import smithy4s.example.greet._
2021
import smithy4s.kinds.PolyFunction
2122
import munit._
2223
import scala.util.{Failure, Success, Try}
@@ -109,4 +110,39 @@ class TransformationSpec() extends FunSuite {
109110

110111
}
111112

113+
test(
114+
"surfacing unhandled errors on an operation without errors doesn't throw"
115+
) {
116+
val e = new Exception("Expected, but unexpected error")
117+
118+
val impl: GreetService[Try] =
119+
new GreetService.Default[Try](Failure(e))
120+
121+
type Result[E, A] = Either[Either[Throwable, E], A]
122+
123+
// If you hit this, you should make sure either that Greet has no errors
124+
// or switch to another operation that matches this assumption.
125+
// Otherwise, this test is useless.
126+
require(
127+
GreetServiceOperation.Greet.error.isEmpty,
128+
"The issue we're testing against can only be reproduced with error-unaware operations."
129+
)
130+
131+
val result = impl
132+
.transform(new Transformation.SurfaceError[Try, Result] {
133+
def apply[E, A](fa: Try[A], projectError: Throwable => Option[E])
134+
: Result[E, A] =
135+
fa match {
136+
case Success(value) => Right(value)
137+
case Failure(exception) =>
138+
projectError(exception).fold[Result[E, A]](Left(Left(exception)))(
139+
e => Left(Right(e))
140+
)
141+
}
142+
})
143+
.greet("hello")
144+
145+
expect.same(result, Left(Left(e)))
146+
}
147+
112148
}

modules/codegen-plugin/src/sbt-test/codegen-plugin/refinements/src/main/scala/refinements/Refinements.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.time.LocalDate
2222
import scala.util.Try
2323
import smithy4s.{Refinement, RefinementProvider}
2424

25+
2526
object Refinements {
2627
object dateFormat {
2728
private def newDate(value: String): Either[String, LocalDate] =
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
> run
1+
> run

modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ private[codegen] class SmithyToIR(
122122
.flatMap(f => DefaultRenderMode.fromString(f.getValue))
123123
.getOrElse(DefaultRenderMode.Full)
124124

125+
private val smithy4sRenderDynamicHintNamespacePatterns
126+
: Set[NamespacePattern] =
127+
model
128+
.getMetadata()
129+
.asScala
130+
.get("smithy4sRenderDynamicHintNamespacePatterns")
131+
.toSet
132+
.flatMap((n: Node) => n.asArrayNode().asScala)
133+
.flatMap(_.getElements().asScala)
134+
.flatMap(_.asStringNode().asScala.map(n => NamespacePattern(n.getValue)))
135+
125136
private def fieldModifier(member: MemberShape): Field.Modifier = {
126137
val hasRequired = member.hasTrait(classOf[RequiredTrait])
127138
val hasNullable = member.hasTrait(classOf[alloy.NullableTrait])
@@ -1360,7 +1371,11 @@ private[codegen] class SmithyToIR(
13601371
private def unfoldTraitNonConstraint(tr: Trait): Hint = {
13611372
val renderDynamic = model
13621373
.expectShape(tr.toShapeId)
1363-
.hasTrait(classOf[smithy4s.meta.RenderAsDynamicBindingTrait])
1374+
.hasTrait(
1375+
classOf[smithy4s.meta.RenderAsDynamicBindingTrait]
1376+
) || smithy4sRenderDynamicHintNamespacePatterns.exists(
1377+
_.matches(tr.toShapeId().namespace)
1378+
)
13641379
if (renderDynamic) Hint.DynamicBinding(tr.toShapeId, tr.toNode)
13651380
else
13661381
Hint.Native(

modules/codegen/test/src/smithy4s/codegen/internals/DynamicHintRenderingSpec.scala

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,71 @@ final class DynamicHintRenderingSpec extends munit.FunSuite {
4141
|
4242
|""".stripMargin
4343

44-
runTest(smithySpec)
44+
runTest(smithySpec)()
45+
}
46+
47+
test("dynamic hint rendering mode - config based - exact match namespace") {
48+
val smithySpec1 =
49+
"""|$version: "2.0"
50+
|
51+
|metadata smithy4sRenderDynamicHintNamespacePatterns = ["test"]
52+
|
53+
|namespace test
54+
|
55+
|@trait
56+
|structure someTrait {
57+
| @required
58+
| int: Integer
59+
|}
60+
|""".stripMargin
61+
62+
val smithySpec2 =
63+
"""|$version: "2.0"
64+
|
65+
|namespace foo
66+
|
67+
|use test#someTrait
68+
|
69+
|@someTrait(int: 1)
70+
|structure Test {
71+
| @someTrait(int: 2)
72+
| one: String
73+
|}
74+
|""".stripMargin
75+
76+
runTest(smithySpec1, smithySpec2)()
77+
}
78+
79+
test("dynamic hint rendering mode - config based - starts with namespace") {
80+
val smithySpec1 =
81+
"""|$version: "2.0"
82+
|
83+
|metadata smithy4sRenderDynamicHintNamespacePatterns = ["test.*"]
84+
|
85+
|namespace test.secondary
86+
|
87+
|@trait
88+
|structure someTrait {
89+
| @required
90+
| int: Integer
91+
|}
92+
|""".stripMargin
93+
94+
val smithySpec2 =
95+
"""|$version: "2.0"
96+
|
97+
|namespace foo
98+
|
99+
|use test.secondary#someTrait
100+
|
101+
|@someTrait(int: 1)
102+
|structure Test {
103+
| @someTrait(int: 2)
104+
| one: String
105+
|}
106+
|""".stripMargin
107+
108+
runTest(smithySpec1, smithySpec2)(ns = "test.secondary")
45109
}
46110

47111
test("dynamic hint rendering mode - enum") {
@@ -64,7 +128,7 @@ final class DynamicHintRenderingSpec extends munit.FunSuite {
64128
|
65129
|""".stripMargin
66130

67-
runTest(smithySpec)
131+
runTest(smithySpec)()
68132
}
69133

70134
test("dynamic hint rendering mode - union") {
@@ -88,7 +152,7 @@ final class DynamicHintRenderingSpec extends munit.FunSuite {
88152
|
89153
|""".stripMargin
90154

91-
runTest(smithySpec)
155+
runTest(smithySpec)()
92156
}
93157

94158
test("dynamic hint rendering mode - primitive") {
@@ -110,7 +174,7 @@ final class DynamicHintRenderingSpec extends munit.FunSuite {
110174
|integer Other
111175
|""".stripMargin
112176

113-
runTest(smithySpec)
177+
runTest(smithySpec)()
114178
}
115179

116180
test("dynamic hint rendering mode - service") {
@@ -135,16 +199,18 @@ final class DynamicHintRenderingSpec extends munit.FunSuite {
135199
|
136200
|""".stripMargin
137201

138-
runTest(smithySpec)
202+
runTest(smithySpec)()
139203
}
140204

141-
private def runTest(smithySpec: String)(implicit loc: Location): Unit = {
205+
private def runTest(
206+
smithySpecs: String*
207+
)(ns: String = "test")(implicit loc: Location): Unit = {
142208
val expect = List(
143-
"""Hints.dynamic(ShapeId("test", "someTrait"), smithy4s.Document.obj("int" -> smithy4s.Document.fromDouble(1.0d)))""",
144-
"""Hints.dynamic(ShapeId("test", "someTrait"), smithy4s.Document.obj("int" -> smithy4s.Document.fromDouble(2.0d)))"""
209+
s"""Hints.dynamic(ShapeId("$ns", "someTrait"), smithy4s.Document.obj("int" -> smithy4s.Document.fromDouble(1.0d)))""",
210+
s"""Hints.dynamic(ShapeId("$ns", "someTrait"), smithy4s.Document.obj("int" -> smithy4s.Document.fromDouble(2.0d)))"""
145211
)
146212

147-
val result = TestUtils.generateScalaCode(smithySpec).values.toList
213+
val result = TestUtils.generateScalaCode(smithySpecs: _*).values.toList
148214

149215
TestUtils.assertContainsSection(
150216
files = result,

modules/codegen/test/src/smithy4s/codegen/internals/TestUtils.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ import software.amazon.smithy.model.Model
2323
object TestUtils {
2424

2525
/** Key is the name (like my.package.name.SomeFile) and the value is the contents of that file */
26-
def generateScalaCode(smithySpec: String): Map[String, String] = {
27-
val model = Model
26+
def generateScalaCode(smithySpecs: String*): Map[String, String] = {
27+
val modelA = Model
2828
.assembler()
2929
.discoverModels()
30-
.addUnparsedModel("foo.smithy", smithySpec)
30+
smithySpecs.zipWithIndex.foreach { case (smithySpec, i) =>
31+
modelA.addUnparsedModel(s"foo$i.smithy", smithySpec)
32+
}
33+
val model = modelA
3134
.assemble()
3235
.unwrap()
3336
generateScalaCode(model)

modules/core/src/smithy4s/Transformation.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ object Transformation {
8888
def apply[I, E, O, SI, SO](op: service.Operation[I, E, O, SI, SO]): G[E,O] = {
8989
val endpoint = service.endpoint(op)
9090
val catcher: Throwable => Option[E] = endpoint.error match {
91-
case None => PartialFunction.empty[Throwable, Option[E]]
91+
case None => Function.const(None)
9292
case Some(value) => value.liftError(_)
9393
}
9494
func.apply(polyFunction(op), catcher)

modules/core/src/smithy4s/schema/Schema.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,15 @@ sealed trait Schema[A]{
154154
final def isUnit: Boolean = this.shapeId == ShapeId("smithy.api", "Unit")
155155

156156
/**
157-
* Turns this schema into an error schema.
157+
* Turns this schema into an error schema, using a partial function.
158158
*/
159159
final def error(unlift: A => Throwable)(lift: PartialFunction[Throwable, A]) : ErrorSchema[A] = ErrorSchema(this, lift.lift, unlift)
160160

161+
/**
162+
* Turns this schema into an error schema.
163+
*/
164+
final def error(unlift: A => Throwable)(lift: Throwable => Option[A]) : ErrorSchema[A] = ErrorSchema(this, lift, unlift)
165+
161166
}
162167

163168
object Schema {

0 commit comments

Comments
 (0)