Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ When adding entries, please treat them as if they could end up in a release any

Thank you!

# 0.18.36

* codegen: Pass the correct ClassLoader to prevent validators/transformers from breaking on externally-defined trait classes in [#1709](https://github.com/disneystreaming/smithy4s/pull/1709)

# 0.18.35

* json, documents: Add support for `@jsonUnknown` in unions (Open Unions) in [#1677](https://github.com/disneystreaming/smithy4s/pull/1677)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
lazy val transformation = project
.settings(
scalaVersion := "2.12.20",
libraryDependencies ++= Seq(
"software.amazon.smithy" % "smithy-build" % "1.57.1",
"ch.epfl.scala" % "spec-traits" % "2.2.0-M2"
)
)

lazy val root = project
.in(file("."))
.enablePlugins(Smithy4sCodegenPlugin)
.settings(
scalaVersion := "3.3.6",
libraryDependencies ++= Seq(
"ch.epfl.scala" % "spec-traits" % "2.2.0-M2" % Smithy4s,
"com.disneystreaming.smithy4s" %% "smithy4s-core" % smithy4sVersion.value
),
Compile / smithy4sModelTransformers := List(
"my-transformation"
),
Compile / smithy4sAllowedNamespaces := List("my.input"),
Compile / smithy4sAllDependenciesAsJars += (transformation / Compile / packageBin).value
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
sys.props.get("plugin.version") match {
case Some(x) =>
addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % x)
case _ =>
sys.error(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021-2025 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.
*/

object Main extends App {
val hints = my.input.MyShape.schema.hints

require(
hints.has[smithy.api.Documentation],
s"Expected to have the Documentation trait, but it was missing: $hints"
)

require(
hints.get[smithy.api.Documentation].get.value == "what's up doc",
s"Documentation trait mismatch: ${hints.get[smithy.api.Documentation].get.value}"
)
println("all good: " + hints.all)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$version: "2"

namespace my.input

@traits#data
document MyShape
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# check if the app runs successfully.
# if it doesn't compile, it could be because the transformation wasn't applied.
Copy link
Member Author

Choose a reason for hiding this comment

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

thought: it is WAY too easy to misconfigure a transformation, hence this extra care

Copy link
Member Author

@kubukoz kubukoz May 15, 2025

Choose a reason for hiding this comment

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

BTW we had no other tests that involved user-provided transformations/validators, apparently. There's no software.amazon.smithy.build.ProjectionTransformer or software.amazon.smithy.model.validation.Validator in the test resources. I think these tests suffice, but maybe we should have a more minimal one for both too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe indeed

# if the transformation throws, it's because of a bug like #336.
> run
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MyTransformation
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2021-2025 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.
*/

import software.amazon.smithy.build.ProjectionTransformer
import software.amazon.smithy.build.TransformContext
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.transform.ModelTransformer
import java.util.function.BiFunction
import software.amazon.smithy.model.traits.Trait
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.traits.DocumentationTrait
import software.amazon.smithy.model.shapes.ShapeId
import bsp.traits.DataTrait

class MyTransformation extends ProjectionTransformer {
def getName(): String = "my-transformation"

// Replace traits#jsonRPC with documentation
def transform(context: TransformContext): Model = {
// this would fail if the class wasn't present on the classpath.

// external shape - regression test for #336
context
.getModel()
.expectShape(ShapeId.from("bsp#BuildTargetData"))
.expectTrait(classOf[DataTrait])

// local shape
context
.getModel()
.expectShape(ShapeId.from("my.input#MyShape"))
.expectTrait(classOf[DataTrait])

ModelTransformer
.create()
.mapTraits(
context.getModel(),
{
case (_, _: DataTrait) =>
new DocumentationTrait("what's up doc")
case (_, trt) => trt
}: BiFunction[Shape, Trait, Trait]
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
lazy val externalLibrary = project
.settings(
autoScalaLibrary := false,
crossPaths := false,
libraryDependencies ++= Seq(
"software.amazon.smithy" % "smithy-model" % "1.57.1"
)
)

lazy val root = project
.in(file("."))
.enablePlugins(Smithy4sCodegenPlugin)
.settings(
scalaVersion := "3.3.6",
libraryDependencies ++= Seq(
"com.disneystreaming.smithy4s" %% "smithy4s-core" % smithy4sVersion.value
),
Compile / smithy4sAllDependenciesAsJars += (externalLibrary / Compile / packageBin).value
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2021-2025 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.
*/

import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.StringTrait;

public class ApiVersionTrait extends StringTrait {

public static ShapeId ID = ShapeId.from("my.input#apiVersion");

public ApiVersionTrait(String value, SourceLocation sourceLocation) {
super(ID, value, sourceLocation);
}

public ApiVersionTrait(String value) {
this(value, SourceLocation.NONE);
}

public static final class Provider extends StringTrait.Provider<ApiVersionTrait> {
public Provider() {
super(ID, ApiVersionTrait::new);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2021-2025 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.
*/

import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ApiVersioningValidator extends AbstractValidator {

@Override
public List<ValidationEvent> validate(Model model) {
model.getShapesWithTrait(ApiVersionTrait.ID).stream().forEach(shape -> {
System.out.println("validating shape: " + shape);
shape.expectTrait(ApiVersionTrait.class);
});

return List.of();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ApiVersionTrait$Provider
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ApiVersioningValidator
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$version: "2"

namespace my.input

@trait(selector: "service")
string apiVersion

@apiVersion("v1")
service FooService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
input.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
sys.props.get("plugin.version") match {
case Some(x) =>
addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % x)
case _ =>
sys.error(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# check if the app compiles.
# if the validator throws, it's because of a bug like #336.
> compile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ package smithy4s.codegen
import sbt.Keys._
import sbt._

import Smithy4sCodegenPlugin.autoImport._
import scala.collection.immutable.ListSet

import Smithy4sCodegenPlugin.autoImport._

private final case class SmithyBuildData(
sources: ListSet[String],
deps: ListSet[String],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

package smithy4s.codegen

import sjsonnew._
import BasicJsonProtocol._
import sbt.FileInfo
import sbt.HashFileInfo
import cats.data.Validated.Invalid
import cats.data.Validated.Valid
import sbt.FileInfo
import sbt.HashFileInfo
import sbt.io.Hash
import sjsonnew._

import BasicJsonProtocol._

// Json codecs used by SBT's caching constructs
private[smithy4s] object JsonConverters {
Expand Down
12 changes: 6 additions & 6 deletions modules/codegen/src/smithy4s/codegen/internals/ModelLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,14 @@ private[codegen] object ModelLoader {
}
}

val validatorClassLoader = locally {
val jarUrls = deps.map(_.toURI().toURL()).toArray
new URLClassLoader(jarUrls, currentClassLoader)
}

// Loading the upstream model
val upstreamModel = Model
.assembler()
.assembler(validatorClassLoader)
Copy link
Member Author

Choose a reason for hiding this comment

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

This should make the classes (validators, trait services) visible to the assembler - but notably not making the models visible.

Copy link
Member Author

Choose a reason for hiding this comment

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

notably this doesn't solve the problems that #1705 and #1707 are attempting to solve, but it's a step in the right direction and doesn't clash with them

// disabling cache to support snapshot-driven experimentation
.putProperty(ModelAssembler.DISABLE_JAR_CACHE, true)
.addClasspathModels(currentClassLoader, discoverModels)
Expand All @@ -95,11 +100,6 @@ private[codegen] object ModelLoader {
case _ => ()
}

val validatorClassLoader = locally {
val jarUrls = deps.map(_.toURI().toURL()).toArray
new URLClassLoader(jarUrls, currentClassLoader)
}

val preTransformationModel =
Model
.assembler(validatorClassLoader)
Expand Down