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

Update to smithy 2.0; add int enum support #352

Merged
merged 6 commits into from
Aug 6, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 5 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ lazy val core = projectMatrix
(ThisBuild / baseDirectory).value / "sampleSpecs" / "packedInputs.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "errors.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "example.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "adtMember.smithy"
(ThisBuild / baseDirectory).value / "sampleSpecs" / "adtMember.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "enums.smithy"
),
(Test / sourceGenerators) := Seq(genSmithyScala(Test).taskValue),
testFrameworks += new TestFramework("weaver.framework.CatsEffect"),
Expand Down Expand Up @@ -617,7 +618,8 @@ lazy val example = projectMatrix
(ThisBuild / baseDirectory).value / "sampleSpecs" / "adtMember.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "brands.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "brandscommon.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "refined.smithy"
(ThisBuild / baseDirectory).value / "sampleSpecs" / "refined.smithy",
(ThisBuild / baseDirectory).value / "sampleSpecs" / "enums.smithy"
),
Compile / resourceDirectory := (ThisBuild / baseDirectory).value / "modules" / "example" / "resources",
isCE3 := true,
Expand Down Expand Up @@ -670,7 +672,7 @@ lazy val Dependencies = new {
)

val Smithy = new {
val smithyVersion = "1.22.0"
val smithyVersion = "1.21.0-rc1"
val model = "software.amazon.smithy" % "smithy-model" % smithyVersion
val build = "software.amazon.smithy" % "smithy-build" % smithyVersion
val awsTraits =
Expand Down
5 changes: 3 additions & 2 deletions modules/codegen/src/smithy4s/codegen/CollisionAvoidance.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object CollisionAvoidance {
case Enumeration(name, originalName, values, hints) =>
val newValues = values.map {
case EnumValue(value, ordinal, name, hints) =>
EnumValue(value, ordinal, name.map(protect), hints.map(modHint))
EnumValue(value, ordinal, protect(name), hints.map(modHint))
}
Enumeration(
protect(name.capitalize),
Expand Down Expand Up @@ -244,7 +244,8 @@ object CollisionAvoidance {
"BigDecimal",
"Map",
"List",
"Set"
"Set",
"None"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if this is the right solution here.. but it works. There was a collision between scala's Option#None and an enum member in the aws-http module

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that's fine

)

private val reservedNames = reservedKeywords ++ reservedTypes
Expand Down
46 changes: 46 additions & 0 deletions modules/codegen/src/smithy4s/codegen/EnumUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.codegen

object EnumUtil {
private def toCamelCase(value: String): String = {
val (_, output) = value.foldLeft((false, "")) {
case ((wasLastSkipped, str), c) =>
if (c.isLetterOrDigit) {
val newC =
if (wasLastSkipped) c.toString.capitalize else c.toString
(false, str + newC)
} else {
(true, str)
}
}
output
}

def enumValueClassName(
name: Option[String],
value: String,
ordinal: Int
) = {
name.getOrElse {
val camel = toCamelCase(value).capitalize
if (camel.nonEmpty) camel else "Value" + ordinal
}

}

}
5 changes: 3 additions & 2 deletions modules/codegen/src/smithy4s/codegen/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ case class Enumeration(
case class EnumValue(
value: String,
ordinal: Int,
name: Option[String],
name: String,
hints: List[Hint] = Nil
)

Expand Down Expand Up @@ -226,6 +226,7 @@ object Hint {
case class Protocol(traits: List[Type.Ref]) extends Hint
// traits that get rendered generically
case class Native(typedNode: Fix[TypedNode]) extends Hint
case object IntEnum extends Hint

sealed trait SpecializedList extends Hint
object SpecializedList {
Expand Down Expand Up @@ -305,7 +306,7 @@ object TypedNode {
ref: Type.Ref,
value: String,
ordinal: Int,
name: Option[String]
name: String
) extends TypedNode[Nothing]
case class StructureTN[A](
ref: Type.Ref,
Expand Down
41 changes: 5 additions & 36 deletions modules/codegen/src/smithy4s/codegen/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,11 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
renderHintsVal(hints),
newline,
values.map { case e @ EnumValue(value, ordinal, _, _) =>
line"""case object ${e.className} extends $name("$value", "${e.className}", $ordinal)"""
line"""case object ${e.name} extends $name("$value", "${e.name}", $ordinal)"""
},
newline,
line"val values: List[$name] = List".args(
values.map(_.className)
values.map(_.name)
),
line"implicit val schema: $Schema_[$name] = enumeration(values).withId(id).addHints(hints)"
)
Expand Down Expand Up @@ -712,37 +712,6 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
}
}

private def toCamelCase(value: String): String = {
val (_, output) = value.foldLeft((false, "")) {
case ((wasLastSkipped, str), c) =>
if (c.isLetterOrDigit) {
val newC =
if (wasLastSkipped) c.toString.capitalize else c.toString
(false, str + newC)
} else {
(true, str)
}
}
output
}

private def enumValueClassName(
name: Option[String],
value: String,
ordinal: Int
) = {
name.getOrElse {
val camel = toCamelCase(value).capitalize
if (camel.nonEmpty) camel else "Value" + ordinal
}

}

private implicit class EnumValueOps(enumValue: EnumValue) {
def className =
enumValueClassName(enumValue.name, enumValue.value, enumValue.ordinal)
}

private def renderNativeHint(hint: Hint.Native): Line =
Line(
smithy4s.recursion
Expand All @@ -753,6 +722,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>

private def renderHint(hint: Hint): Option[Line] = hint match {
case h: Hint.Native => renderNativeHint(h).some
case Hint.IntEnum => line"smithy4s.IntEnum()".some
case _ => None
}

Expand Down Expand Up @@ -803,9 +773,8 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
}

private def renderTypedNode(tn: TypedNode[CString]): CString = tn match {
case EnumerationTN(ref, value, ordinal, name) =>
val className = enumValueClassName(name, value, ordinal)
(ref.show + "." + className + ".widen").write
case EnumerationTN(ref, _, _, name) =>
(ref.show + "." + name + ".widen").write
case StructureTN(ref, fields) =>
val fieldStrings = fields.map {
case (_, FieldTN.RequiredTN(value)) => value.runDefault
Expand Down
148 changes: 125 additions & 23 deletions modules/codegen/src/smithy4s/codegen/SmithyToIR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,71 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
def toIRVisitor(
renderAdtMemberStructures: Boolean
): ShapeVisitor[Option[Decl]] =
new ShapeVisitor.Default[Option[Decl]] {
new ShapeVisitor[Option[Decl]] {

def getDefault(shape: Shape): Option[Decl] = {
private def getDefault(shape: Shape): Option[Decl] = {
val hints = traitsToHints(shape.getAllTraits().asScala.values.toList)

if (shape.isMemberShape()) None
else
shape.tpe.flatMap {
case Type.Alias(_, name, tpe: Type.ExternalType, isUnwrapped) =>
val newHints = hints.filterNot(_ == tpe.refinementHint)
TypeAlias(name, name, tpe, isUnwrapped, newHints).some
case Type.Alias(_, name, tpe, isUnwrapped) =>
TypeAlias(name, name, tpe, isUnwrapped, hints).some
case Type.PrimitiveType(_) => None
case other =>
TypeAlias(
shape.name,
shape.name,
other,
isUnwrapped = false,
hints
).some
}
shape.tpe.flatMap {
case Type.Alias(_, name, tpe: Type.ExternalType, isUnwrapped) =>
val newHints = hints.filterNot(_ == tpe.refinementHint)
TypeAlias(name, name, tpe, isUnwrapped, newHints).some
case Type.Alias(_, name, tpe, isUnwrapped) =>
TypeAlias(name, name, tpe, isUnwrapped, hints).some
case Type.PrimitiveType(_) => None
case other =>
TypeAlias(
shape.name,
shape.name,
other,
isUnwrapped = false,
hints
).some
}
}

override def blobShape(x: BlobShape): Option[Decl] = getDefault(x)

override def booleanShape(x: BooleanShape): Option[Decl] = getDefault(x)

override def listShape(x: ListShape): Option[Decl] = getDefault(x)

override def setShape(x: SetShape): Option[Decl] = getDefault(x)

override def mapShape(x: MapShape): Option[Decl] = getDefault(x)

override def byteShape(x: ByteShape): Option[Decl] = getDefault(x)

override def shortShape(x: ShortShape): Option[Decl] = getDefault(x)

override def integerShape(x: IntegerShape): Option[Decl] = getDefault(x)

override def longShape(x: LongShape): Option[Decl] = getDefault(x)

override def floatShape(x: FloatShape): Option[Decl] = getDefault(x)

override def documentShape(x: DocumentShape): Option[Decl] = getDefault(x)

override def doubleShape(x: DoubleShape): Option[Decl] = getDefault(x)

override def bigIntegerShape(x: BigIntegerShape): Option[Decl] =
getDefault(x)

override def bigDecimalShape(x: BigDecimalShape): Option[Decl] =
getDefault(x)

override def operationShape(x: OperationShape): Option[Decl] = getDefault(
x
)

override def resourceShape(x: ResourceShape): Option[Decl] = getDefault(x)

override def memberShape(x: MemberShape): Option[Decl] = None

override def timestampShape(x: TimestampShape): Option[Decl] = getDefault(
x
)

override def structureShape(shape: StructureShape): Option[Decl] = {
val rec = isRecursive(shape.getId())

Expand Down Expand Up @@ -126,13 +166,49 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
.asScala
.zipWithIndex
.map { case (value, index) =>
EnumValue(value.getValue(), index, value.getName().asScala)
EnumValue(
value.getValue(),
index,
EnumUtil.enumValueClassName(
value.getName().asScala,
value.getValue,
index
)
)
}
.toList
Enumeration(shape.name, shape.name, values).some
Enumeration(shape.name, shape.name, values, hints(shape)).some
case _ => this.getDefault(shape)
})

override def enumShape(shape: EnumShape): Option[Decl] = {
val values = shape
.getEnumValues()
.asScala
.zipWithIndex
.map { case ((name, value), index) =>
EnumValue(value, index, name)
}
.toList
Enumeration(shape.name, shape.name, values).some
}

override def intEnumShape(shape: IntEnumShape): Option[Decl] = {
val values = shape
.getEnumValues()
.asScala
.map { case (name, value) =>
EnumValue(name, value, name)
}
.toList
Enumeration(
shape.name,
shape.name,
values,
hints(shape) :+ Hint.IntEnum
).some
}

override def serviceShape(shape: ServiceShape): Option[Decl] = {
val generalErrors: List[Type] =
shape
Expand Down Expand Up @@ -426,6 +502,9 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {

def serviceShape(x: ServiceShape): Option[Type] = None

override def enumShape(x: EnumShape): Option[Type] =
Type.Ref(x.namespace, x.name).some

def stringShape(x: StringShape): Option[Type] = x match {
case T.enumeration(_) => Type.Ref(x.namespace, x.name).some
case shape if shape.getId() == uuidShapeId =>
Expand Down Expand Up @@ -484,6 +563,9 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
}
}

@annotation.nowarn(
"msg=class UniqueItemsTrait in package traits is deprecated"
)
private val traitToHint: PartialFunction[Trait, Hint] = {
case _: ErrorTrait => Hint.Error
case t: ProtocolDefinitionTrait =>
Expand Down Expand Up @@ -710,7 +792,27 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
ref,
enumDef.getValue(),
index,
enumDef.getName().asScala
EnumUtil.enumValueClassName(
enumDef.getName().asScala,
enumDef.getValue,
index
)
)
case (N.StringNode(str), UnRef(S.Enumeration(enumeration))) =>
val ((enumName, enumValue), index) =
enumeration
.getEnumValues()
.asScala
.zipWithIndex
.find { case ((_, value), _) => value == str }
.get
val shapeId = enumeration.getId()
val ref = Type.Ref(shapeId.getNamespace(), shapeId.getName())
TypedNode.EnumerationTN(
ref,
enumValue,
index,
enumName
)
// List
case (
Expand Down
Loading