Skip to content

Commit

Permalink
Merge pull request #193 from sideeffffect/guardrailDiscoveredOpenApiF…
Browse files Browse the repository at this point in the history
…iles

Allow automatic discovery of OpenAPI spec files
  • Loading branch information
blast-hardcheese authored Aug 21, 2022
2 parents ce29ecf + 6ee5268 commit 669b600
Show file tree
Hide file tree
Showing 17 changed files with 1,642 additions and 289 deletions.
13 changes: 5 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '8', '11', '15' ]
java: [ '8', '11', '17' ]
scala: [ '2.12.16' ]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v3.4.1
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: print Java version
run: java -version
- uses: actions/cache@v1
with:
path: ~/.cache/coursier
key: ${{ runner.os }}-scala-${{ matrix.scala }}-${{ hashFiles('**/*.sbt') }}
restore-keys: |
${{ runner.os }}-scala-${{ matrix.scala }}-
- name: Coursier cache
uses: coursier/[email protected]
- name: Run tests
run: sbt ++${{ matrix.scala }} "^ test; scripted"
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ jobs:
- name: print GnuPG version
run: gpg --version
- name: Set up JDK 8
uses: actions/setup-java@v1
uses: actions/setup-java@v3.4.1
with:
distribution: 'temurin'
java-version: 8
- name: Publish artifacts
run: sbt ci-release publishLegacy
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ addSbtPlugin("dev.guardrail" % "sbt-guardrail" % "<Please use the latest availab
```

### `build.sbt`
```
```scala
/* Available arguments:
specPath: java.io.File
pkg: String
Expand All @@ -39,3 +39,12 @@ guardrailTasks in Compile := List(
...
)
```
Alternatively use the `guardrailDiscoveredOpenApiFiles` setting to automatically discover OpenAPI spec files under `src/main/openapi` or `src/test/openapi` (see the [test for full example](src/sbt-test/sbt-guardrail/scala-client-codegen-app/build.sbt)):
```scala
Compile / guardrailTasks := (Compile / guardrailDiscoveredOpenApiFiles).value.flatMap { openApiFile =>
List(
ScalaClient(openApiFile.file, pkg = openApiFile.pkg, framework = "http4s"),
ScalaServer(openApiFile.file, pkg = openApiFile.pkg, framework = "http4s")
)
}
```
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ git.useGitDescribe := true

git.gitDescribedVersion := git.gitDescribedVersion(v => {
import scala.sys.process._
val nativeGitDescribeResult = ("git describe --tags HEAD" !!).trim
val nativeGitDescribeResult = ("git describe --tags --always HEAD" !!).trim
git.defaultTagByVersionStrategy(nativeGitDescribeResult)
}).value

Expand All @@ -53,7 +53,7 @@ val commonSettings = Seq(


scriptedLaunchOpts := { scriptedLaunchOpts.value ++
Seq("-Xmx1024M", "-XX:MaxPermSize=256M", "-Dplugin.version=" + version.value)
Seq("-Xmx1024M", "-Dplugin.version=" + version.value)
}

scriptedDependencies := {
Expand Down
3 changes: 2 additions & 1 deletion modules/core/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ libraryDependencies ++= Seq(
"dev.guardrail" %% "guardrail-java-spring-mvc" % "0.71.2",
"dev.guardrail" %% "guardrail-scala-akka-http" % "0.75.0",
"dev.guardrail" %% "guardrail-scala-dropwizard" % "0.72.0",
"dev.guardrail" %% "guardrail-scala-http4s" % "0.75.0"
"dev.guardrail" %% "guardrail-scala-http4s" % "0.75.0",
"org.snakeyaml" % "snakeyaml-engine" % "2.3"
)

buildInfoKeys := Seq[BuildInfoKey](organization, version)
Expand Down
21 changes: 15 additions & 6 deletions modules/core/src/main/scala/AbstractCodegenPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ trait AbstractGuardrailPlugin extends GuardrailRunner { self: AutoPlugin =>

trait guardrailAutoImport {
val guardrailDefaults = Keys.guardrailDefaults
val guardrailDiscoveredOpenApiFiles = Keys.guardrailDiscoveredOpenApiFiles
val guardrailTasks = Keys.guardrailTasks
val guardrail = Keys.guardrail

Expand Down Expand Up @@ -182,6 +183,8 @@ trait AbstractGuardrailPlugin extends GuardrailRunner { self: AutoPlugin =>
def authImplementationNative = Keys.authImplementationNative
def authImplementationSimple = Keys.authImplementationSimple
def authImplementationCustom = Keys.authImplementationCustom

lazy val GuardrailHelpers = _root_.dev.guardrail.sbt.GuardrailHelpers
}

private def cachedGuardrailTask(projectName: String, scope: String, scalaBinaryVersion: String)(kind: String, streams: _root_.sbt.Keys.TaskStreams)(tasks: List[(String, Args)], sources: Seq[java.io.File]) = {
Expand Down Expand Up @@ -215,12 +218,18 @@ trait AbstractGuardrailPlugin extends GuardrailRunner { self: AutoPlugin =>
cachedResult(()).products
}

def scopedSettings(name: String, scope: Configuration) = Seq(
scope / Keys.guardrailTasks := List.empty,
scope / Keys.guardrail := cachedGuardrailTask(SbtKeys.name.value, scope.name, SbtKeys.scalaBinaryVersion.value)(name, _root_.sbt.Keys.streams.value)((scope / Keys.guardrailTasks).value, (scope / SbtKeys.managedSourceDirectories).value),
scope / SbtKeys.sourceGenerators += (scope / Keys.guardrail).taskValue,
scope / SbtKeys.watchSources ++= Tasks.watchSources((scope / Keys.guardrailTasks).value),
)
def scopedSettings(name: String, scope: Configuration) = {
import _root_.sbt.Keys.{resourceDirectory, sourceDirectory, unmanagedResourceDirectories, unmanagedSourceDirectories}
Seq(
scope / unmanagedSourceDirectories += (scope / sourceDirectory).value / "openapi",
scope / unmanagedResourceDirectories += (scope / resourceDirectory).value / "openapi",
scope / Keys.guardrailDiscoveredOpenApiFiles := GuardrailHelpers.discoverOpenApiFiles((scope / sourceDirectory).value / "openapi"),
scope / Keys.guardrailTasks := List.empty,
scope / Keys.guardrail := cachedGuardrailTask(SbtKeys.name.value, scope.name, SbtKeys.scalaBinaryVersion.value)(name, _root_.sbt.Keys.streams.value)((scope / Keys.guardrailTasks).value, (scope / SbtKeys.managedSourceDirectories).value),
scope / SbtKeys.sourceGenerators += (scope / Keys.guardrail).taskValue,
scope / SbtKeys.watchSources ++= Tasks.watchSources((scope / Keys.guardrailTasks).value),
)
}

override lazy val projectSettings = {
scopedSettings("compile", Compile) ++ scopedSettings("test", Test)
Expand Down
57 changes: 57 additions & 0 deletions modules/core/src/main/scala/GuardrailHelpers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dev.guardrail
package sbt

import _root_.sbt._

object GuardrailHelpers {

private def dropExtension(filePath: String): String = {
filePath.split('.').toList match {
case l@List() => l
case l@List(_) => l
case l => l.dropRight(1)
}
}.mkString(".")

private def recursiveListFiles(f: File): List[File] = {
val these = Option(f.listFiles).toList.flatten
these.filter(f => f.isFile) ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}

def isOpenApiSpec(file: File): Boolean = {
import org.snakeyaml.engine.v2.api.{Load, LoadSettings}

import java.util.{Map => JMap}
import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.util.{Try, Using}

Using(Source.fromFile(file)) { source =>
val reader = source.bufferedReader()
val docs = new Load(LoadSettings.builder().setAllowDuplicateKeys(true).setAllowRecursiveKeys(true).build()).loadAllFromReader(reader).asScala
val yamls = docs.flatMap(d => Try(d.asInstanceOf[JMap[String, Any]].asScala).toOption).toList
yamls.exists(m => m.contains("openapi") || m.contains("swagger"))
}.toOption.getOrElse(false)
}

case class DiscoveredFile(base: File, file: File, fileRelative: File) {
val fileRelativePath: String = fileRelative.getPath
val fileRelativePathWithoutExtension: String = dropExtension(fileRelative.getPath)
val pkg: String = fileRelativePathWithoutExtension.replace(Path.sep, '.')
}

def discoverFiles(base: File): List[DiscoveredFile] = recursiveListFiles(base).flatMap { file =>
file.relativeTo(base).map { fileRelative =>
DiscoveredFile(base, file, fileRelative)
}
}

def discoverOpenApiFiles(base: File): List[DiscoveredFile] =
discoverFiles(base).filter(f => isOpenApiSpec(f.file))

def createGuardrailTasks(
sourceDirectory: File,
)(discoveredFileToTasks: DiscoveredFile => List[dev.guardrail.sbt.Types.Args]): List[dev.guardrail.sbt.Types.Args] =
discoverOpenApiFiles(sourceDirectory).flatMap(discoveredFileToTasks)

}
1 change: 1 addition & 0 deletions modules/core/src/main/scala/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ object Keys {
}

val guardrailDefaults = SettingKey[Args]("guardrail-defaults")
val guardrailDiscoveredOpenApiFiles = SettingKey[List[GuardrailHelpers.DiscoveredFile]]("guardrail-discovered-open-api-files")
val guardrailTasks = SettingKey[List[Types.Args]]("guardrail-tasks")
val guardrail = TaskKey[Seq[File]](
"guardrail",
Expand Down
Loading

0 comments on commit 669b600

Please sign in to comment.