From 09ac23b3260e01d66fe07506e4e673d6629cf7fd Mon Sep 17 00:00:00 2001 From: yisraelu Date: Wed, 19 Jul 2023 14:37:18 -0400 Subject: [PATCH 1/4] add nostacktrace meta trait --- .../internals/CollisionAvoidance.scala | 1 + .../src/smithy4s/codegen/internals/IR.scala | 1 + .../smithy4s/codegen/internals/Renderer.scala | 6 ++- .../codegen/internals/SmithyToIR.scala | 3 ++ ...re.amazon.smithy.model.traits.TraitService | 1 + .../META-INF/smithy/smithy4s.meta.smithy | 5 ++ .../src/smithy4s/meta/NoStackTraceTrait.java | 48 +++++++++++++++++++ sampleSpecs/errorHandling.smithy | 5 ++ 8 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 modules/protocol/src/smithy4s/meta/NoStackTraceTrait.java diff --git a/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala b/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala index 4e3a48bd8..73e43f450 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala @@ -337,6 +337,7 @@ private[internals] object CollisionAvoidance { val option = reserved("scala", "Option") val none = reserved("scala", "None") val some = reserved("scala", "Some") + val noStackTrace = reserved("scala.util.control", "NoStackTrace") } diff --git a/modules/codegen/src/smithy4s/codegen/internals/IR.scala b/modules/codegen/src/smithy4s/codegen/internals/IR.scala index 9c5f1fc5e..10968e434 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/IR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/IR.scala @@ -274,6 +274,7 @@ private[internals] sealed trait Hint private[internals] object Hint { case object Trait extends Hint case object Error extends Hint + case object NoStackTrace extends Hint case object PackedInputs extends Hint case object NoDefault extends Hint case object ErrorMessage extends Hint diff --git a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala index 241b73081..89f0f0c23 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala @@ -620,11 +620,15 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => lines( if (hints.contains(Hint.Error)) { + val throwable = + if (hints.contains(Hint.NoStackTrace)) + "scala.util.control.NoStackTrace" + else "Throwable" val mixinExtensions = if (mixins.nonEmpty) { val ext = mixins.map(m => line"$m").intercalate(line" with ") line" with $ext" } else Line.empty - block(line"$decl extends Throwable$mixinExtensions") { + block(line"$decl extends $throwable$mixinExtensions") { fields .find { f => f.hints.contains_(Hint.ErrorMessage) || diff --git a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala index e4e3abf89..a2d972944 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala @@ -21,6 +21,7 @@ import cats.implicits._ import smithy4s.meta.AdtMemberTrait import smithy4s.meta.ErrorMessageTrait import smithy4s.meta.IndexedSeqTrait +import smithy4s.meta.NoStackTraceTrait import smithy4s.meta.PackedInputsTrait import smithy4s.meta.RefinementTrait import smithy4s.meta.VectorTrait @@ -917,6 +918,8 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) { Hint.Deprecated(d.getMessage.asScala, d.getSince.asScala) case _: ErrorMessageTrait => Hint.ErrorMessage + case _: NoStackTraceTrait => + Hint.NoStackTrace case _: VectorTrait => Hint.SpecializedList.Vector case _: IndexedSeqTrait => diff --git a/modules/protocol/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/modules/protocol/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService index e5fd1725b..e879e46a2 100644 --- a/modules/protocol/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService +++ b/modules/protocol/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -3,6 +3,7 @@ smithy4s.meta.AdtMemberTrait$Provider smithy4s.meta.VectorTrait$Provider smithy4s.meta.IndexedSeqTrait$Provider smithy4s.meta.ErrorMessageTrait$Provider +smithy4s.meta.NoStackTraceTrait$Provider smithy4s.meta.RefinementTrait$Provider smithy4s.meta.UnwrapTrait$Provider smithy4s.meta.AdtTrait$Provider diff --git a/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy b/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy index b20add37f..f60ff5b3d 100644 --- a/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy +++ b/modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy @@ -149,3 +149,8 @@ structure typeclass { /// include a Service Product version of the service. @trait(selector: ":is(service)") structure generateServiceProduct {} + +/// Placing this trait on an error will cause the generated code to exclude the stacktrace +/// via extending scala.util.control.NoStackTrace instead of Throwable. +@trait(selector: "structure :is([trait|error])") +structure noStackTrace {} diff --git a/modules/protocol/src/smithy4s/meta/NoStackTraceTrait.java b/modules/protocol/src/smithy4s/meta/NoStackTraceTrait.java new file mode 100644 index 000000000..028fc11d4 --- /dev/null +++ b/modules/protocol/src/smithy4s/meta/NoStackTraceTrait.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smithy4s.meta; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AnnotationTrait; +import software.amazon.smithy.model.traits.AbstractTrait; + +public class NoStackTraceTrait extends AnnotationTrait { + + public static ShapeId ID = ShapeId.from("smithy4s.meta#noStackTrace"); + + public NoStackTraceTrait(ObjectNode node) { + super(ID, node); + } + + public NoStackTraceTrait() { + super(ID, Node.objectNode()); + } + + public static final class Provider extends AbstractTrait.Provider { + public Provider() { + super(ID); + } + + @Override + public NoStackTraceTrait createTrait(ShapeId target, Node node) { + return new NoStackTraceTrait(node.expectObjectNode()); + } + } +} + diff --git a/sampleSpecs/errorHandling.smithy b/sampleSpecs/errorHandling.smithy index 03ad99835..a3216b522 100644 --- a/sampleSpecs/errorHandling.smithy +++ b/sampleSpecs/errorHandling.smithy @@ -2,6 +2,8 @@ $version: "2" namespace smithy4s.example +use smithy4s.meta#noStackTrace + // Where the errors are placed in this spec doesn't necessarily "make sense" but is // mixed up to test some specific scenarios such as falling back to service-level // errors when no operation-level ones are found. @@ -22,17 +24,20 @@ operation ErrorHandlingOperation { errors: [EHNotFound, EHFallbackServerError] } +@noStackTrace @httpError(404) @error("client") structure EHNotFound { message: String } +@noStackTrace @error("client") structure EHFallbackClientError { message: String } +@noStackTrace @httpError(503) @error("server") structure EHServiceUnavailable { From 25e1b4fd48c1f6d2394ec870bf6551b8572b643e Mon Sep 17 00:00:00 2001 From: yisraelu Date: Wed, 19 Jul 2023 23:09:37 -0400 Subject: [PATCH 2/4] update bootstrap files --- .../src/generated/smithy4s/example/EHFallbackClientError.scala | 2 +- .../src/generated/smithy4s/example/EHNotFound.scala | 2 +- .../src/generated/smithy4s/example/EHServiceUnavailable.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala index d297866e7..83c22e9cf 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala @@ -7,7 +7,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHFallbackClientError(message: Option[String] = None) extends Throwable { +final case class EHFallbackClientError(message: Option[String] = None) extends scala.util.control.NoStackTrace { override def getMessage(): String = message.orNull } object EHFallbackClientError extends ShapeTag.Companion[EHFallbackClientError] { diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala index 52f4cdff3..daf549ea2 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala @@ -7,7 +7,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHNotFound(message: Option[String] = None) extends Throwable { +final case class EHNotFound(message: Option[String] = None) extends scala.util.control.NoStackTrace { override def getMessage(): String = message.orNull } object EHNotFound extends ShapeTag.Companion[EHNotFound] { diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala index 728fde9be..a705474dd 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala @@ -7,7 +7,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHServiceUnavailable(message: Option[String] = None) extends Throwable { +final case class EHServiceUnavailable(message: Option[String] = None) extends scala.util.control.NoStackTrace { override def getMessage(): String = message.orNull } object EHServiceUnavailable extends ShapeTag.Companion[EHServiceUnavailable] { From fb3ab88984f75aa8341e2533b73e45372573c086 Mon Sep 17 00:00:00 2001 From: yisraelu Date: Mon, 24 Jul 2023 18:05:23 -0400 Subject: [PATCH 3/4] clean up auto import namespace --- .../smithy4s/example/EHFallbackClientError.scala | 3 ++- .../src/generated/smithy4s/example/EHNotFound.scala | 3 ++- .../smithy4s/example/EHServiceUnavailable.scala | 3 ++- .../codegen/internals/CollisionAvoidance.scala | 2 ++ .../src/smithy4s/codegen/internals/LineSegment.scala | 10 ++++++++-- .../src/smithy4s/codegen/internals/Renderer.scala | 8 ++++---- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala index 83c22e9cf..220254b7b 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHFallbackClientError.scala @@ -1,5 +1,6 @@ package smithy4s.example +import scala.util.control.NoStackTrace import smithy4s.Hints import smithy4s.Schema import smithy4s.ShapeId @@ -7,7 +8,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHFallbackClientError(message: Option[String] = None) extends scala.util.control.NoStackTrace { +final case class EHFallbackClientError(message: Option[String] = None) extends NoStackTrace { override def getMessage(): String = message.orNull } object EHFallbackClientError extends ShapeTag.Companion[EHFallbackClientError] { diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala index daf549ea2..bf8a9ceb5 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHNotFound.scala @@ -1,5 +1,6 @@ package smithy4s.example +import scala.util.control.NoStackTrace import smithy4s.Hints import smithy4s.Schema import smithy4s.ShapeId @@ -7,7 +8,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHNotFound(message: Option[String] = None) extends scala.util.control.NoStackTrace { +final case class EHNotFound(message: Option[String] = None) extends NoStackTrace { override def getMessage(): String = message.orNull } object EHNotFound extends ShapeTag.Companion[EHNotFound] { diff --git a/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala b/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala index a705474dd..0925a49c5 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/EHServiceUnavailable.scala @@ -1,5 +1,6 @@ package smithy4s.example +import scala.util.control.NoStackTrace import smithy4s.Hints import smithy4s.Schema import smithy4s.ShapeId @@ -7,7 +8,7 @@ import smithy4s.ShapeTag import smithy4s.schema.Schema.string import smithy4s.schema.Schema.struct -final case class EHServiceUnavailable(message: Option[String] = None) extends scala.util.control.NoStackTrace { +final case class EHServiceUnavailable(message: Option[String] = None) extends NoStackTrace { override def getMessage(): String = message.orNull } object EHServiceUnavailable extends ShapeTag.Companion[EHServiceUnavailable] { diff --git a/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala b/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala index be53cdf70..3b03d2e18 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/CollisionAvoidance.scala @@ -328,6 +328,8 @@ private[internals] object CollisionAvoidance { val option = NameRef("scala", "Option") val none = NameRef("scala", "None") val some = NameRef("scala", "Some") + val noStackTrace = NameRef("scala.util.control", "NoStackTrace") + val throwable = NameRef("java.lang", "Throwable") } diff --git a/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala b/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala index 09be3b1a9..7bb92d52c 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala @@ -57,8 +57,8 @@ private[codegen] object LineSegment { def asImport: String = s"${(pkg :+ getNamePrefix).mkString(".")}" def isAutoImported: Boolean = { - val value = pkg.mkString(".") - value.startsWith("scala") || value.equalsIgnoreCase("java.lang") + val value = pkg.mkString(".") + NameRef.autoImportedNames.exists(_.equalsIgnoreCase(value)) } def getNamePrefix: String = name.split("\\.").head def +(piece: String): NameRef = { @@ -79,6 +79,12 @@ private[codegen] object LineSegment { } object NameRef { + val autoImportedNames: List[String] = List( + "scala", + "java.lang", + "scala.Predef", + "scala.collection.immutable" + ) implicit val nameRefShow: Show[NameRef] = Show.show[NameRef](_.asImport) def apply(pkg: String, name: String): NameRef = NameRef(pkg.split("\\.").toList, name, List.empty) diff --git a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala index bde24bea4..2e36869bc 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala @@ -628,15 +628,15 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => lines( if (hints.contains(Hint.Error)) { - val throwable = + val exception = if (hints.contains(Hint.NoStackTrace)) - "scala.util.control.NoStackTrace" - else "Throwable" + noStackTrace + else throwable val mixinExtensions = if (mixins.nonEmpty) { val ext = mixins.map(m => line"$m").intercalate(line" with ") line" with $ext" } else Line.empty - block(line"$decl extends $throwable$mixinExtensions") { + block(line"$decl extends $exception$mixinExtensions") { fields .find { f => f.hints.contains_(Hint.ErrorMessage) || From 346bf556b99f4d1b91276e4c33910e6c449ab213 Mon Sep 17 00:00:00 2001 From: yisraelu Date: Tue, 25 Jul 2023 11:10:30 -0400 Subject: [PATCH 4/4] format --- .../codegen/src/smithy4s/codegen/internals/LineSegment.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala b/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala index 7bb92d52c..6b1f0be7f 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/LineSegment.scala @@ -57,8 +57,8 @@ private[codegen] object LineSegment { def asImport: String = s"${(pkg :+ getNamePrefix).mkString(".")}" def isAutoImported: Boolean = { - val value = pkg.mkString(".") - NameRef.autoImportedNames.exists(_.equalsIgnoreCase(value)) + val value = pkg.mkString(".") + NameRef.autoImportedNames.exists(_.equalsIgnoreCase(value)) } def getNamePrefix: String = name.split("\\.").head def +(piece: String): NameRef = {