Skip to content

Commit

Permalink
Merge pull request #223 from disneystreaming/decouple-json-schema-fro…
Browse files Browse the repository at this point in the history
…m-openapi

Decouple `openapi` and `json-schema` modules
  • Loading branch information
daddykotex authored Dec 12, 2023
2 parents ac452fe + e4be6ce commit c9f3d62
Show file tree
Hide file tree
Showing 109 changed files with 1,235 additions and 875 deletions.
40 changes: 27 additions & 13 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,35 @@ val scala212 = "2.12.18"
val scala213 = "2.13.12"
val scalaVersions = List(scala213, scala212)

object `compiler-core` extends Cross[CompilerCoreModule](scalaVersions)
trait CompilerCoreModule
extends CrossScalaModule
with BaseScalaModule
with BasePublishModule {

def publishArtifactName = "smithytranslate-compiler-core"

def moduleDeps = Seq(traits)

def ivyDeps = Agg(
ivy"com.fasterxml.jackson.core:jackson-databind:2.15.3",
buildDeps.smithy.model,
buildDeps.smithy.build,
buildDeps.cats.mtl,
buildDeps.ciString,
buildDeps.slf4j,
buildDeps.alloy.core,
buildDeps.collectionsCompat
)

}

object `json-schema` extends Cross[JsonSchemaModule](scalaVersions)
trait JsonSchemaModule
extends CrossScalaModule
with BaseScalaModule
with BasePublishModule {
def moduleDeps = Seq(openapi())
def moduleDeps = Seq(`compiler-core`())

def publishArtifactName = "smithytranslate-json-schema"

Expand All @@ -52,19 +75,10 @@ trait OpenApiModule

def publishArtifactName = "smithytranslate-openapi"

def ivyDeps = buildDeps.swagger.parser ++ Agg(
buildDeps.smithy.model,
buildDeps.smithy.build,
buildDeps.cats.mtl,
buildDeps.ciString,
buildDeps.slf4j,
buildDeps.alloy.core,
buildDeps.collectionsCompat
)
def moduleDeps = Seq(`compiler-core`())

def moduleDeps = Seq(
traits
)
def ivyDeps =
buildDeps.swagger.parser

object tests extends this.ScalaTests with BaseMunitTests {
def ivyDeps = super.ivyDeps() ++ Agg(
Expand Down
8 changes: 4 additions & 4 deletions modules/cli/src/runners/FileUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ package cli
package runners
import cats.data
import cats.data.NonEmptyList
import smithytranslate.compiler.FileContents

object FileUtils {
def readAll(
paths: NonEmptyList[os.Path],
includedExtensions: List[String]
): List[(data.NonEmptyList[String], String)] = {
): List[FileContents] = {
paths.toList.flatMap { path =>
if (os.isDir(path)) {
val files = os
.walk(path)
.filter(p => includedExtensions.contains(p.ext))
files.map { in =>
pathToNel(in, path) -> os
.read(in)
FileContents(pathToNel(in, path), os.read(in))
}.toList
} else {
List((NonEmptyList.of(path.last), os.read(path)))
List(FileContents(NonEmptyList.of(path.last), os.read(path)))
}
}
}
Expand Down
28 changes: 18 additions & 10 deletions modules/cli/src/runners/openapi/ParseAndCompile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ package smithytranslate.cli.runners.openapi

import cats.data.NonEmptyList
import smithytranslate.cli.runners.FileUtils.readAll
import smithytranslate.openapi.OpenApiCompiler
import smithytranslate.compiler.openapi.OpenApiCompiler
import smithytranslate.cli.transformer.TranslateTransformer
import software.amazon.smithy.model.Model
import smithytranslate.json_schema.JsonSchemaCompiler
import smithytranslate.compiler.json_schema.JsonSchemaCompiler
import smithytranslate.compiler.ToSmithyResult
import smithytranslate.compiler.ToSmithyCompilerOptions
import smithytranslate.compiler.openapi.OpenApiCompilerInput
import smithytranslate.compiler.json_schema.JsonSchemaCompilerInput

object ParseAndCompile {
def openapi(
Expand All @@ -31,18 +35,20 @@ object ParseAndCompile {
transformers: List[TranslateTransformer],
useEnumTraitSyntax: Boolean,
debug: Boolean
): OpenApiCompiler.Result[Model] = {
): ToSmithyResult[Model] = {
val includedExtensions = List("yaml", "yml", "json")
val inputs = readAll(inputPaths, includedExtensions)
val opts = OpenApiCompiler.Options(
val input = OpenApiCompilerInput.UnparsedSpecs(
readAll(inputPaths, includedExtensions)
)
val opts = ToSmithyCompilerOptions(
useVerboseNames,
validateInput,
validateOutput,
transformers,
useEnumTraitSyntax,
debug
)
OpenApiCompiler.parseAndCompile(opts, inputs: _*)
OpenApiCompiler.compile(opts, input)
}

def jsonSchema(
Expand All @@ -53,18 +59,20 @@ object ParseAndCompile {
transformers: List[TranslateTransformer],
useEnumTraitSyntax: Boolean,
debug: Boolean
): OpenApiCompiler.Result[Model] = {
): ToSmithyResult[Model] = {
val includedExtensions = List("json")
val inputs = readAll(inputPaths, includedExtensions)
val opts = OpenApiCompiler.Options(
val input = JsonSchemaCompilerInput.UnparsedSpecs(
readAll(inputPaths, includedExtensions)
)
val opts = ToSmithyCompilerOptions(
useVerboseNames,
validateInput,
validateOutput,
transformers,
useEnumTraitSyntax,
debug
)
JsonSchemaCompiler.parseAndCompile(opts, inputs: _*)
JsonSchemaCompiler.compile(opts, input)
}

}
9 changes: 5 additions & 4 deletions modules/cli/src/runners/openapi/ReportResult.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@

package smithytranslate.cli.runners.openapi

import smithytranslate.openapi.OpenApiCompiler
import smithytranslate.compiler.openapi.OpenApiCompiler
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer
import scala.jdk.CollectionConverters._
import software.amazon.smithy.build.transforms.FilterSuppressions
import software.amazon.smithy.build.TransformContext
import software.amazon.smithy.model.node.{Node, ObjectNode}
import smithytranslate.compiler.ToSmithyResult

final case class ReportResult(outputPath: os.Path, outputJson: Boolean) {

Expand Down Expand Up @@ -75,9 +76,9 @@ final case class ReportResult(outputPath: os.Path, outputJson: Boolean) {
(path, os.Source.WritableSource(in._2))
}

def apply(result: OpenApiCompiler.Result[Model], debug: Boolean): Unit = {
def apply(result: ToSmithyResult[Model], debug: Boolean): Unit = {
result match {
case OpenApiCompiler.Failure(error, modelErrors) =>
case ToSmithyResult.Failure(error, modelErrors) =>
val message = if (modelErrors.isEmpty) {
"An error occurred while importing your Open API resources."
} else {
Expand All @@ -92,7 +93,7 @@ final case class ReportResult(outputPath: os.Path, outputJson: Boolean) {
} else {
System.err.println(error.getMessage())
}
case OpenApiCompiler.Success(modelErrors, model) =>
case ToSmithyResult.Success(modelErrors, model) =>
modelErrors.foreach(e => System.err.println(e.getMessage()))
val smithyFiles =
if (outputJson) getSmithyJsonFiles(model) else getSmithyFiles(model)
Expand Down
105 changes: 105 additions & 0 deletions modules/compiler-core/src/AbstractToSmithyCompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* Copyright 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 smithytranslate.compiler

import scala.jdk.OptionConverters._
import software.amazon.smithy.model.{Model => SmithyModel}
import cats.syntax.all._
import cats.data.Chain
import smithytranslate.compiler.internals.IModel
import smithytranslate.compiler.internals.IModelPostProcessor
import smithytranslate.compiler.internals.IModelToSmithy
import cats.data.NonEmptyList
import scala.jdk.CollectionConverters._
import software.amazon.smithy.model.validation.Severity
import software.amazon.smithy.model.validation.ValidatedResult
import software.amazon.smithy.build.TransformContext

/** Holds common logic that serves for the conversion of openapi/json-schema to smithy
*/
abstract class AbstractToSmithyCompiler[Input] protected[compiler] () {

protected def convertToInternalModel(
opts: ToSmithyCompilerOptions,
input: Input
): (Chain[ToSmithyError], IModel)

def compile(
opts: ToSmithyCompilerOptions,
input: Input
): ToSmithyResult[SmithyModel] = {
val (errors0, smithy0) = convertToInternalModel(opts, input)
.map(IModelPostProcessor(opts.useVerboseNames))
.map(new IModelToSmithy(opts.useEnumTraitSyntax))
val translationErrors = errors0.toList

val assembled: ValidatedResult[SmithyModel] = validate(smithy0)
val validationEvents = if (opts.debug) {
assembled.getValidationEvents()
} else {
val errorEvents = assembled.getValidationEvents(Severity.ERROR)
val dangerEvents = assembled.getValidationEvents(Severity.DANGER)
val criticalErrors = new java.util.ArrayList(errorEvents)
criticalErrors.addAll(dangerEvents)
criticalErrors
}
val problematicSeverities = Set(Severity.DANGER, Severity.ERROR)
val hasProblematicEvents = validationEvents
.iterator()
.asScala
.map(_.getSeverity())
.exists(problematicSeverities)

val allErrors =
if (hasProblematicEvents) {
val smithyValidationFailed =
ToSmithyError.SmithyValidationFailed(validationEvents.asScala.toList)
smithyValidationFailed :: translationErrors
} else {
translationErrors
}

val transformedModel =
assembled.getResult().toScala.map(transform(opts))

(transformedModel, NonEmptyList.fromList(allErrors)) match {
case (None, Some(nonEmptyErrors)) =>
ToSmithyResult.Failure(nonEmptyErrors.head, nonEmptyErrors.tail)
case (None, None) =>
ToSmithyResult.Failure(
ToSmithyError.SmithyValidationFailed(Nil),
translationErrors
)
case (Some(model), None) =>
ToSmithyResult.Success(translationErrors, model)
case (Some(model), Some(nonEmptyErrors)) =>
if (opts.validateOutput) {
ToSmithyResult.Failure(nonEmptyErrors.head, nonEmptyErrors.tail)
} else {
ToSmithyResult.Success(allErrors, model)
}
}
}

private def validate(model: SmithyModel): ValidatedResult[SmithyModel] =
SmithyModel.assembler().discoverModels().addModel(model).assemble()

private def transform(opts: ToSmithyCompilerOptions)(model: SmithyModel): SmithyModel =
opts.transformers.foldLeft(model)((m, t) =>
t.transform(TransformContext.builder().model(m).build())
)

}
20 changes: 20 additions & 0 deletions modules/compiler-core/src/FileContents.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Copyright 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 smithytranslate.compiler

import cats.data.NonEmptyList

final case class FileContents(path: NonEmptyList[String], content: String)
38 changes: 38 additions & 0 deletions modules/compiler-core/src/SmithyVersion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Copyright 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 smithytranslate.compiler

sealed abstract class SmithyVersion extends Product with Serializable {
override def toString(): String = this match {
case SmithyVersion.One => "1.0"
case SmithyVersion.Two => "2.0"
}
}
object SmithyVersion {
case object One extends SmithyVersion
case object Two extends SmithyVersion

def fromString(string: String): Either[String, SmithyVersion] = {
val versionOne = Set("1", "1.0")
val versionTwo = Set("2", "2.0")
if (versionOne(string)) Right(SmithyVersion.One)
else if (versionTwo(string)) Right(SmithyVersion.Two)
else
Left(
s"expected one of ${versionOne ++ versionTwo}, but got '$string'"
)
}
}
27 changes: 27 additions & 0 deletions modules/compiler-core/src/ToSmithyCompilerOptions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* Copyright 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 smithytranslate.compiler

import software.amazon.smithy.build.ProjectionTransformer

final case class ToSmithyCompilerOptions(
useVerboseNames: Boolean,
validateInput: Boolean,
validateOutput: Boolean,
transformers: List[ProjectionTransformer],
useEnumTraitSyntax: Boolean,
debug: Boolean
)
Loading

0 comments on commit c9f3d62

Please sign in to comment.