Skip to content

Commit

Permalink
Merge pull request #1470 from disneystreaming/smithy4s-schema-to-smithy
Browse files Browse the repository at this point in the history
Add functionality to recreate a smithy model from smithy4s schemas/services
  • Loading branch information
Baccata authored Apr 6, 2024
2 parents da4b23a + 6da2464 commit cfe7acb
Show file tree
Hide file tree
Showing 13 changed files with 1,086 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 0.18.16

* Add support for converting smithy4s services and schemas to smithy models
* Add `smithy4s.meta#only` annotation allowing to filter operations in
services, intended to reduce the amount of code generated from AWS specs

Expand Down
9 changes: 8 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,9 @@ lazy val dynamic = projectMatrix
Compile / smithySpecs := Seq(
(ThisBuild / baseDirectory).value / "modules" / "dynamic" / "smithy" / "dynamic.smithy"
),
Test / unmanagedClasspath ++= Seq(
(ThisBuild / baseDirectory).value / "sampleSpecs"
),
Compile / sourceGenerators := Seq(genSmithyScala(Compile).taskValue),
Compile / packageSrc / mappings ++= {
val base = (Compile / sourceManaged).value
Expand All @@ -583,7 +586,11 @@ lazy val dynamic = projectMatrix
.jvmPlatform(
allJvmScalaVersions,
jvmDimSettings ++ Seq(
libraryDependencies += Dependencies.Smithy.model
libraryDependencies ++= Seq(
Dependencies.Smithy.model,
Dependencies.Smithy.diff % Test,
Dependencies.Smithy.build % Test
)
)
)
.jsPlatform(allJsScalaVersions, jsDimSettings)
Expand Down
45 changes: 45 additions & 0 deletions modules/docs/markdown/06-guides/schema-to-smithy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
sidebar_label: Smithy4s to Smithy
title: Converting Smithy4s Schemas and Services to Smithy
---

Using the smithy4s dynamic module you can convert a smithy4s service or schema into a smithy model. This guide will walk through the steps to do this.

## Creating a DynamicSchemaIndex

The first step is to take the services and schemas you'd like included in your smithy model and add them to a DynamicSchemaIndex using the provided builder.

```scala mdoc
import smithy4s.dynamic.DynamicSchemaIndex

val dynamicSchemaIndex = DynamicSchemaIndex.builder
.addService[smithy4s.example.KVStoreGen]
.addSchema[smithy4s.example.FaceCard]
.build()
```

## Converting to Smithy Model

Now that we have a DynamicSchemaIndex, we can convert to a smithy model object from the smithy-model Java library. This feature is only supported on the JVM and not ScalaJS or Scala Native.

```scala mdoc
val model = dynamicSchemaIndex.toSmithyModel
```

## Rendering as a String

If you wish to render the smithy `Model` as a String, smithy-model provides a method to accomplish this.

```scala mdoc
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer
import scala.jdk.CollectionConverters._
import java.nio.file.Path

val smithyFiles: Map[Path, String] =
SmithyIdlModelSerializer
.builder()
.build()
.serialize(model)
.asScala
.toMap
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2021-2024 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.dynamic

private[dynamic] trait DynamicSchemaIndexCompanionPlatform {}
43 changes: 43 additions & 0 deletions modules/dynamic/src-jvm/DynamicSchemaIndexCompanionPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2021-2024 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.dynamic

import software.amazon.smithy.model.shapes.ModelSerializer
import software.amazon.smithy.model.transform.ModelTransformer

private[dynamic] trait DynamicSchemaIndexCompanionPlatform {
self: DynamicSchemaIndex.type =>

/**
* Loads a dynamic schema index model from a smithy model.
*/
def loadModel(
model: software.amazon.smithy.model.Model
): DynamicSchemaIndex = {
val flattenedModel =
ModelTransformer.create().flattenAndRemoveMixins(model);
val node = ModelSerializer.builder().build.serialize(flattenedModel)
val document = NodeToDocument(node)
smithy4s.Document
.decode[smithy4s.dynamic.model.Model](document)
.map(load(_)) match {
case Left(error) => throw error
case Right(value) => value
}
}

}
103 changes: 87 additions & 16 deletions modules/dynamic/src-jvm/DynamicSchemaIndexPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,99 @@

package smithy4s.dynamic

import software.amazon.smithy.model.shapes.ModelSerializer
import software.amazon.smithy.model.transform.ModelTransformer
import smithy4s.Service
import smithy4s.schema.OperationSchema
import software.amazon.smithy.model.shapes.Shape
import smithy4s.schema.Schema
import software.amazon.smithy.model.shapes.OperationShape
import scala.jdk.CollectionConverters._
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.Model

private[dynamic] trait DynamicSchemaIndexPlatform {
self: DynamicSchemaIndex.type =>
self: DynamicSchemaIndex =>

import smithy4s.dynamic.internals.conversion.syntax._

/**
* Loads a dynamic schema index model from a smithy model.
* Convert the `DynamicSchemaIndex` into a smithy `Model`.
*/
def loadModel(
model: software.amazon.smithy.model.Model
): DynamicSchemaIndex = {
val flattenedModel =
ModelTransformer.create().flattenAndRemoveMixins(model);
val node = ModelSerializer.builder().build.serialize(flattenedModel)
val document = NodeToDocument(node)
smithy4s.Document
.decode[smithy4s.dynamic.model.Model](document)
.map(load(_)) match {
case Left(error) => throw error
case Right(value) => value
def toSmithyModel: Model = {
val allShapes =
self.allSchemas.flatMap(fromSchema(_)) ++
self.allServices.flatMap(s => fromService(s.service))
Model.builder().addShapes(allShapes.toSeq: _*).build()
}

private def fromService[Alg[_[_, _, _, _, _]]](
service: Service[Alg]
): List[Shape] = {
val allOperationResults =
service.endpoints.map(e => fromOperationSchema(e.schema))

val serviceShape = addTraits(
ServiceShape
.builder()
.id(service.id.toSmithy)
.operations(allOperationResults.map(_.operationShape.getId).asJava)
.build(),
service.hints
)

allOperationResults
.flatMap(op => op.transitiveShapes :+ op.operationShape)
.toList :+ serviceShape
}

private case class OperationSchemaResult(
operationShape: OperationShape,
transitiveShapes: List[Shape]
)
private def fromOperationSchema[I, E, O, SI, SO](
opSchema: OperationSchema[I, E, O, SI, SO]
): OperationSchemaResult = {
val transitives = fromSchemas(
List(opSchema.input, opSchema.output) ++ opSchema.error.toList
.flatMap(_.alternatives.map(_.schema))
.toList
)

val errors = opSchema.error.toList.flatMap(e =>
e.alternatives.map(_.schema.shapeId.toSmithy)
)

val opShape =
addTraits(
OperationShape
.builder()
.id(opSchema.id.toSmithy)
.addErrors(errors.asJava)
.input(opSchema.input.shapeId.toSmithy)
.output(opSchema.output.shapeId.toSmithy)
.build(),
opSchema.hints
)

OperationSchemaResult(opShape, transitives)
}

private def fromSchemas(schemas: List[Schema[_]]): List[Shape] = {
schemas.flatMap { schema =>
schema
.compile(internals.conversion.ToSmithyVisitor)
.apply(Map.empty)
._1
.values
.toList
}
}

private def fromSchema[A](schema: Schema[A]): List[Shape] = {
schema
.compile(internals.conversion.ToSmithyVisitor)
.apply(Map.empty)
._1
.values
.toList
}
}
Loading

0 comments on commit cfe7acb

Please sign in to comment.