diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..858b9c8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Scala Steward: Reformat with scalafmt 3.10.0 +66e3da45361c46fbdd09aea04f34bc5fc9c2d4e6 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f624b9b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,189 @@ +# This file was automatically generated by sbt-github-actions using the +# githubWorkflowGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the workflow description +# to meet your needs, then regenerate this file. + +name: Continuous Integration + +on: + pull_request: + branches: ['**', '!update/**', '!pr/**'] + push: + branches: ['**', '!update/**', '!pr/**'] + tags: [v*] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + +concurrency: + group: ${{ github.workflow }} @ ${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Test + strategy: + matrix: + os: [ubuntu-22.04] + scala: [3] + java: [temurin@17] + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' + run: sbt +update + + - name: Check that workflows are up to date + run: sbt githubWorkflowCheck + + - name: Check headers and formatting + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' + run: sbt '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck + + - name: Test + run: sbt '++ ${{ matrix.scala }}' test + + - name: Check binary compatibility + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' + run: sbt '++ ${{ matrix.scala }}' mimaReportBinaryIssues + + - name: Generate API documentation + if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04' + run: sbt '++ ${{ matrix.scala }}' doc + + - name: Make target directories + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + run: mkdir -p transformation/target project/target + + - name: Compress target directories + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + run: tar cf targets.tar transformation/target project/target + + - name: Upload target directories + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + uses: actions/upload-artifact@v5 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} + path: targets.tar + + publish: + name: Publish Artifacts + needs: [build] + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + strategy: + matrix: + os: [ubuntu-22.04] + java: [temurin@17] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' + run: sbt +update + + - name: Download target directories (3) + uses: actions/download-artifact@v6 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3 + + - name: Inflate target directories (3) + run: | + tar xf targets.tar + rm targets.tar + + - name: Import signing key + if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + run: echo $PGP_SECRET | base64 -d -i - | gpg --import + + - name: Import signing key and strip passphrase + if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + run: | + echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg + echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg + (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) + + - name: Publish + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} + run: sbt tlCiRelease + + dependency-submission: + name: Submit Dependencies + if: github.event.repository.fork == false && github.event_name != 'pull_request' + strategy: + matrix: + os: [ubuntu-22.04] + java: [temurin@17] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' + run: sbt +update + + - name: Submit Dependencies + uses: scalacenter/sbt-dependency-submission@v2 + with: + modules-ignore: root_3 tests_3 smithy4sexample_3 + configs-ignore: test scala-tool scala-doc-tool test-internal diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml new file mode 100644 index 0000000..547aaa4 --- /dev/null +++ b/.github/workflows/clean.yml @@ -0,0 +1,59 @@ +# This file was automatically generated by sbt-github-actions using the +# githubWorkflowGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the workflow description +# to meet your needs, then regenerate this file. + +name: Clean + +on: push + +jobs: + delete-artifacts: + name: Delete Artifacts + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Delete artifacts + run: | + # Customize those three lines with your repository and credentials: + REPO=${GITHUB_API_URL}/repos/${{ github.repository }} + + # A shortcut to call GitHub API. + ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } + + # A temporary file which receives HTTP response headers. + TMPFILE=/tmp/tmp.$$ + + # An associative array, key: artifact name, value: number of artifacts of that name. + declare -A ARTCOUNT + + # Process all artifacts on this repository, loop on returned "pages". + URL=$REPO/actions/artifacts + while [[ -n "$URL" ]]; do + + # Get current page, get response headers in a temporary file. + JSON=$(ghapi --dump-header $TMPFILE "$URL") + + # Get URL of next page. Will be empty if we are at the last page. + URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') + rm -f $TMPFILE + + # Number of artifacts on this page: + COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) + + # Loop on all artifacts on this page. + for ((i=0; $i < $COUNT; i++)); do + + # Get name of artifact and count instances of this name. + name=$(jq <<<$JSON -r ".artifacts[$i].name?") + ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) + + id=$(jq <<<$JSON -r ".artifacts[$i].id?") + size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) + printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size + ghapi -X DELETE $REPO/actions/artifacts/$id + done + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f62fc50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.direnv/ +.scala-build/ +**/target +.bsp/ + +node_modules/ +.smithy.lsp.log +build/smithy +transformed +**/test/**/actual.smithy diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000..179b890 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,41 @@ +# This file was automatically generated by sbt-typelevel-mergify using the +# mergifyGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the mergify configuration +# to meet your needs, then regenerate this file. + +pull_request_rules: +- name: merge scala-steward's PRs + conditions: + - author=scala-steward + - or: + - body~=labels:.*early-semver-patch + - body~=labels:.*early-semver-minor + - status-success=Test (ubuntu-22.04, 3, temurin@17) + actions: + merge: {} +- name: Label smithy4sExample PRs + conditions: + - files~=^smithy4sExample/ + actions: + label: + add: + - smithy4sExample + remove: [] +- name: Label tests PRs + conditions: + - files~=^tests/ + actions: + label: + add: + - tests + remove: [] +- name: Label transformation PRs + conditions: + - files~=^transformation/ + actions: + label: + add: + - transformation + remove: [] diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..3cb4c4c --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,36 @@ +version = 3.11.0 +runner.dialect=scala3 + +runner.dialectOverride.allowSignificantIndentation = false +runner.dialectOverride.allowQuietSyntax = true + +maxColumn = 100 +align.preset = some + +newlines.beforeMultiline = unfold +newlines.topLevelStatements = [before, after] +newlines.topLevelStatementsMinBreaks = 2 +newlines.implicitParamListModifierForce = [before] +continuationIndent.defnSite = 2 +continuationIndent.extendSite = 2 +optIn.breakChainOnFirstMethodDot = true +includeCurlyBraceInSelectChains = true +includeNoParensInSelectChains = true + +trailingCommas = "multiple" + +rewrite.rules = [ + RedundantBraces, + RedundantParens, + ExpandImportSelectors, + PreferCurlyFors +] + +runner.optimizer.forceConfigStyleMinArgCount = 3 +danglingParentheses.defnSite = true +danglingParentheses.callSite = true +danglingParentheses.exclude = [ + "`trait`" +] +verticalMultiline.newlineAfterOpenParen = true +verticalMultiline.atDefnSite = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..390e6c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + Copyright 2026 Jakub Kozłowski + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..c8d2c65 --- /dev/null +++ b/build.sbt @@ -0,0 +1,85 @@ +ThisBuild / tlBaseVersion := "0.1" +ThisBuild / organization := "org.polyvariant.smithy-transformations" +ThisBuild / organizationName := "Polyvariant" +ThisBuild / startYear := Some(2026) +ThisBuild / licenses := Seq(License.Apache2) +ThisBuild / developers := List(tlGitHubDev("kubukoz", "Jakub Kozłowski")) + +ThisBuild / githubWorkflowPublishTargetBranches := Seq( + RefPredicate.Equals(Ref.Branch("main")), + RefPredicate.StartsWith(Ref.Tag("v")), +) + +ThisBuild / scalaVersion := "3.8.3" +ThisBuild / tlJdkRelease := Some(17) +ThisBuild / tlFatalWarnings := false +ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots + +ThisBuild / mergifyStewardConfig ~= (_.map(_.withMergeMinors(true))) + +val smithyVersion = "1.70.0" + +val commonSettings = Seq( + scalacOptions -= "-Xkind-projector:underscores", + scalacOptions ++= Seq( + "-Xkind-projector", + "-deprecation", + "-Wunused:all", + "-Wnonunit-statement", + ), + libraryDependencies ++= Seq( + "org.scalameta" %%% "munit" % "1.3.0" % Test + ), +) + +lazy val transformation = project + .settings( + autoScalaLibrary := false, + crossPaths := false, + libraryDependencies ++= Seq( + "software.amazon.smithy" % "smithy-build" % smithyVersion, + "software.amazon.smithy" % "smithy-model" % smithyVersion, + ), + smithyTraitCodegenNamespace := "smithytransformations", + smithyTraitCodegenJavaPackage := "smithytransformations", + smithyTraitCodegenDependencies := Nil, + javacOptions -= "-Xlint:all", + Compile / doc / javacOptions ++= Seq( + // skip "no comment" warnings in Javadoc, these Java files are just boilerplate + "-Xdoclint:all,-missing" + ), + ) + .enablePlugins(SmithyTraitCodegenPlugin) + +lazy val tests = project + .settings( + commonSettings, + libraryDependencies ++= Seq( + "software.amazon.smithy" % "smithy-build" % smithyVersion, + "software.amazon.smithy" % "smithy-syntax" % smithyVersion, + "software.amazon.smithy" % "smithy-diff" % smithyVersion % Test, + "com.lihaoyi" %% "os-lib" % "0.11.8" % Test, + ), + publish / skip := true, + ) + .dependsOn(transformation) + .enablePlugins(NoPublishPlugin) + +lazy val smithy4sExample = project + .settings( + commonSettings, + libraryDependencies ++= Seq( + "com.disneystreaming.smithy4s" %%% "smithy4s-core" % smithy4sVersion.value + ), + Compile / smithy4sModelTransformers := List( + "addOperations" + ), + Compile / smithy4sAllDependenciesAsJars += (transformation / Compile / packageBin).value, + ) + .enablePlugins(Smithy4sCodegenPlugin) + .enablePlugins(NoPublishPlugin) + +lazy val root = project + .in(file(".")) + .aggregate(transformation, tests, smithy4sExample) + .enablePlugins(NoPublishPlugin) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..dabdb15 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.12.11 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..a1796db --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,6 @@ +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.5") +addSbtPlugin("org.typelevel" % "sbt-typelevel-mergify" % "0.8.5") + +addSbtPlugin("org.polyvariant" % "smithy-trait-codegen-sbt" % "0.2.3") + +addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.19.4") diff --git a/smithy4sExample/src/main/smithy/example.smithy b/smithy4sExample/src/main/smithy/example.smithy new file mode 100644 index 0000000..629f901 --- /dev/null +++ b/smithy4sExample/src/main/smithy/example.smithy @@ -0,0 +1,14 @@ +$version: "2" + +namespace example + +use smithytransformations#addOperations + +@addOperations([Another]) +service MyService { + operations: [A] +} + +operation A {} + +operation Another {} diff --git a/smithy4sExample/src/test/scala/example/MyServiceTest.scala b/smithy4sExample/src/test/scala/example/MyServiceTest.scala new file mode 100644 index 0000000..de80c17 --- /dev/null +++ b/smithy4sExample/src/test/scala/example/MyServiceTest.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2026 Polyvariant + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 example + +import munit.FunSuite + +class MyServiceTest extends FunSuite { + test("MyService.endpoints contains both A and Another after addOperations runs") { + val endpointNames = MyServiceGen.endpoints.map(_.id.name).toSet + assertEquals(endpointNames, Set("A", "Another")) + } +} diff --git a/tests/src/test/resources/smithy/addOperations/apply/expected.smithy b/tests/src/test/resources/smithy/addOperations/apply/expected.smithy new file mode 100644 index 0000000..4cf2c5b --- /dev/null +++ b/tests/src/test/resources/smithy/addOperations/apply/expected.smithy @@ -0,0 +1,16 @@ +$version: "2" + +namespace example + +use smithytransformations#addOperations + +@addOperations([Another, Third]) +service MyService { + operations: [A, Another, Third] +} + +operation A {} + +operation Another {} + +operation Third {} diff --git a/tests/src/test/resources/smithy/addOperations/apply/input.smithy b/tests/src/test/resources/smithy/addOperations/apply/input.smithy new file mode 100644 index 0000000..f5bf405 --- /dev/null +++ b/tests/src/test/resources/smithy/addOperations/apply/input.smithy @@ -0,0 +1,18 @@ +$version: "2" + +namespace example + +use smithytransformations#addOperations + +@addOperations([Another]) +service MyService { + operations: [A] +} + +apply MyService @addOperations([Third]) + +operation A {} + +operation Another {} + +operation Third {} diff --git a/tests/src/test/resources/smithy/addOperations/basic/expected.smithy b/tests/src/test/resources/smithy/addOperations/basic/expected.smithy new file mode 100644 index 0000000..e9ae7ef --- /dev/null +++ b/tests/src/test/resources/smithy/addOperations/basic/expected.smithy @@ -0,0 +1,14 @@ +$version: "2" + +namespace example + +use smithytransformations#addOperations + +@addOperations([Another]) +service MyService { + operations: [A, Another] +} + +operation A {} + +operation Another {} diff --git a/tests/src/test/resources/smithy/addOperations/basic/input.smithy b/tests/src/test/resources/smithy/addOperations/basic/input.smithy new file mode 100644 index 0000000..629f901 --- /dev/null +++ b/tests/src/test/resources/smithy/addOperations/basic/input.smithy @@ -0,0 +1,14 @@ +$version: "2" + +namespace example + +use smithytransformations#addOperations + +@addOperations([Another]) +service MyService { + operations: [A] +} + +operation A {} + +operation Another {} diff --git a/tests/src/test/scala/smithytransformations/AddOperationsTest.scala b/tests/src/test/scala/smithytransformations/AddOperationsTest.scala new file mode 100644 index 0000000..f43ca8c --- /dev/null +++ b/tests/src/test/scala/smithytransformations/AddOperationsTest.scala @@ -0,0 +1,103 @@ +/* + * Copyright 2026 Polyvariant + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 smithytransformations + +import munit.FunSuite +import software.amazon.smithy.build.TransformContext +import software.amazon.smithy.diff.ModelDiff +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.loader.IdlTokenizer +import software.amazon.smithy.model.loader.ModelAssembler +import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer +import software.amazon.smithy.syntax +import software.amazon.smithy.syntax.TokenTree + +import java.nio.file.Paths +import scala.jdk.CollectionConverters.* + +class AddOperationsTest extends FunSuite { + + test("basic: adds @addOperations targets to the service's operations") { + transformationComparisonTest(os.sub / "addOperations" / "basic") + } + + test("apply: a second `apply ... @addOperations(...)` is concatenated with the original") { + transformationComparisonTest(os.sub / "addOperations" / "apply") + } + + private def transformationComparisonTest(directory: os.SubPath) = { + val result = new AddOperations().transform( + TransformContext + .builder() + .model(loadModel(os.resource / "smithy" / directory / "input.smithy")) + .build() + ) + + val expected = loadModel(os.resource / "smithy" / directory / "expected.smithy") + val diff = + ModelDiff + .builder() + .oldModel(expected) + .newModel(result) + .compare() + .getDiffEvents + .asScala + .toList + + val actualFile = + os.pwd / "tests" / "src" / "test" / "resources" / "smithy" / directory / "actual.smithy" + + if (diff.nonEmpty) { + os.write + .over( + actualFile, + format( + SmithyIdlModelSerializer + .builder() + .build() + .serialize(result) + .get(Paths.get("sample.smithy")) + ), + ) + println(s"wrote actual contents to $actualFile") + } else if (os.exists(actualFile)) { + os.remove(actualFile): Unit + } + + assert(diff.isEmpty, diff.map(_.toString).mkString("\n")) + } + + private def format(string: String): String = { + val tokenizer = IdlTokenizer.create(string) + val tree = TokenTree.of(tokenizer) + syntax.Formatter.format(tree) + } + + private def loadModel(resources: os.ResourcePath*): Model = { + val assembler = Model + .assembler() + .discoverModels() + .putProperty(ModelAssembler.DISABLE_JAR_CACHE, true) + + resources.foreach { res => + assembler.addImport(this.getClass.getClassLoader.getResource(res.segments.mkString("/"))) + } + + assembler.assemble().unwrap() + } + +} diff --git a/transformation/src/main/java/smithytransformations/AddOperations.java b/transformation/src/main/java/smithytransformations/AddOperations.java new file mode 100644 index 0000000..978b4ff --- /dev/null +++ b/transformation/src/main/java/smithytransformations/AddOperations.java @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Polyvariant + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 smithytransformations; + +import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.transform.ModelTransformer; + +import java.util.HashSet; +import java.util.Set; + +public final class AddOperations implements ProjectionTransformer { + + @Override + public String getName() { + return "addOperations"; + } + + @Override + public Model transform(TransformContext context) { + Model model = context.getModel(); + Set updated = new HashSet<>(); + + for (ServiceShape service : model.getServiceShapesWithTrait(AddOperationsTrait.class)) { + AddOperationsTrait trait = service.expectTrait(AddOperationsTrait.class); + ServiceShape.Builder builder = service.toBuilder(); + trait.getValues().forEach(builder::addOperation); + updated.add(builder.build()); + } + + if (updated.isEmpty()) { + return model; + } + + return ModelTransformer.create().replaceShapes(model, updated); + } +} diff --git a/transformation/src/main/resources/META-INF/services/software.amazon.smithy.build.ProjectionTransformer b/transformation/src/main/resources/META-INF/services/software.amazon.smithy.build.ProjectionTransformer new file mode 100644 index 0000000..cdac755 --- /dev/null +++ b/transformation/src/main/resources/META-INF/services/software.amazon.smithy.build.ProjectionTransformer @@ -0,0 +1 @@ +smithytransformations.AddOperations diff --git a/transformation/src/main/resources/META-INF/smithy/manifest b/transformation/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 0000000..cd8469e --- /dev/null +++ b/transformation/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +smithytransformations.smithy diff --git a/transformation/src/main/resources/META-INF/smithy/smithytransformations.smithy b/transformation/src/main/resources/META-INF/smithy/smithytransformations.smithy new file mode 100644 index 0000000..c467a04 --- /dev/null +++ b/transformation/src/main/resources/META-INF/smithy/smithytransformations.smithy @@ -0,0 +1,10 @@ +$version: "2" + +namespace smithytransformations + +/// Adds the listed operations to the service this trait is applied to. +@trait(selector: "service") +list addOperations { + @idRef(failWhenMissing: true, selector: "operation") + member: String +}