Skip to content

Commit

Permalink
Merge pull request #879 from tg44/openapi-codegen-b3
Browse files Browse the repository at this point in the history
Openapi codegen improvements - docs and sbt oprions
  • Loading branch information
adamw authored Dec 7, 2020
2 parents 8fd4264 + 3d232c8 commit ed46bc8
Show file tree
Hide file tree
Showing 23 changed files with 372 additions and 19 deletions.
2 changes: 1 addition & 1 deletion doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
6 changes: 4 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
extensions = [
'sphinx_markdown_tables',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
Expand Down Expand Up @@ -182,4 +184,4 @@ def setup(app):
'auto_toc_tree_section': 'Contents',
'enable_auto_doc_ref': False
}, True)
app.add_transform(AutoStructify)
app.add_transform(AutoStructify)
62 changes: 62 additions & 0 deletions doc/generator/sbt-openapi-codegen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Generate endpoint definitions from an OpenAPI YAML

```eval_rst
.. note::
This is a really early alpha implementation.
```

## Installation steps

Add the sbt plugin to the `project/plugins.sbt`:

```scala
addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "@VERSION@")
```

Enable the plugin for your project in the `build.sbt`:

```scala
enablePlugins(OpenapiCodegenPlugin)
```

Add your OpenApi file to the project, and override the `openapiSwaggerFile` setting in the `build.sbt`:

```scala
openapiSwaggerFile := baseDirectory.value / "swagger.yaml"
```

At this point your compile step will try to generate the endpoint definitions
to the `sttp.tapir.generated.TapirGeneratedEndpoints` object, where you can access the
defined case-classes and endpoint definitions.

## Usage and options

The generator currently supports these settings, you can override them in the `build.sbt`;

| setting | default value | description |
|---|---|---|
| openapiSwaggerFile | baseDirectory.value / "swagger.yaml" | The swagger file with the api definitions. |
| openapiPackage | sttp.tapir.generated | The name for the generated package. |
| openapiObject | TapirGeneratedEndpoints | The name for the generated object. |

The general usage is;

```scala
import sttp.tapir.generated._
import sttp.tapir.docs.openapi._
import sttp.tapir.openapi.circe.yaml._

val docs = TapirGeneratedEndpoints.generatedEndpoints.toOpenAPI("My Bookshop", "1.0")
```

### Limitations

Currently, the generated code depends on `"io.circe" %% "circe-generic"`. In the future probably we will make the encoder/decoder json lib configurable (PRs welcome).

We currently miss a lot of OpenApi features like:
- tags
- enums/ADTs
- missing model types and meta descriptions (like date, minLength)
- file handling

6 changes: 6 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ Development and maintenance of sttp tapir is sponsored by [SoftwareMill](https:/
testing
.. toctree::
:maxdepth: 2
:caption: Generators
generator/sbt-openapi-codegen
.. toctree::
:maxdepth: 2
:caption: Other subjects
Expand Down
3 changes: 2 additions & 1 deletion doc/requirements.pip
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
sphinx_rtd_theme==0.4.3
recommonmark==0.5.0
sphinx==2.0.1
sphinx-autobuild==0.7.1
sphinx-autobuild==0.7.1
sphinx-markdown-tables==0.0.15
17 changes: 17 additions & 0 deletions sbt/sbt-openapi-codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Developer notes

#### Testing
```
sbt
project openapiCodegen2_12
test
scripted
```

#### Local debugging
```
cd sbt/sbt-openapi-codegen/src/sbt-test/sbt-openapi-codegen/minimal/
sbt -Dplugin.version=0.1-SNAPSHOT run
cat target/swagger.yaml
cat target/scala-2.12/classes/sttp/tapir/generated/TapirGeneratedEndpoints.scala
```
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import sbt._

trait OpenapiCodegenKeys {
lazy val swaggerFile = settingKey[File]("swagger file with the definitions")
lazy val openapiSwaggerFile = settingKey[File]("The swagger file with the api definitions.")
lazy val openapiPackage = settingKey[String]("The name for the generated package.")
lazy val openapiObject = settingKey[String]("The name for the generated object.")

lazy val generateTapirDefinitions = taskKey[Unit]("Generates tapir definitions based on the input swagger file")
lazy val generateTapirDefinitions = taskKey[Unit]("The task that generates tapir definitions based on the input swagger file.")
}

object OpenapiCodegenKeys extends OpenapiCodegenKeys
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,29 @@ object OpenapiCodegenPlugin extends AutoPlugin {
)

def openapiCodegenDefaultSettings: Seq[Setting[_]] = Seq(
swaggerFile := baseDirectory.value / "swagger.yaml"
openapiSwaggerFile := baseDirectory.value / "swagger.yaml",
openapiPackage := "sttp.tapir.generated",
openapiObject := "TapirGeneratedEndpoints"
)

private def codegen = Def.task {
val log = sLog.value
log.info("Zipping file...")
(((
swaggerFile,
openapiSwaggerFile,
openapiPackage,
openapiObject,
sourceManaged,
streams
) flatMap {
(
swaggerFile: File,
packageName: String,
objectName: String,
srcDir: File,
taskStreams: TaskStreams
) =>
OpenapiCodegenTask(swaggerFile, srcDir, taskStreams.cacheDirectory).file
OpenapiCodegenTask(swaggerFile, packageName, objectName, srcDir, taskStreams.cacheDirectory).file
}) map (Seq(_))).value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import codegen._

case class OpenapiCodegenTask(
inputYaml: File,
packageName: String,
objectName: String,
dir: File,
cacheDir: File
) {

import FileInfo.hash
import Tracked.inputChanged

val tempFile = cacheDir / "sbt-openapi-codegen" / s"TapirGeneratedEndpoints.scala"
val outFile = dir / "sbt-openapi-codegen" / s"TapirGeneratedEndpoints.scala"
val tempFile = cacheDir / "sbt-openapi-codegen" / s"$objectName.scala"
val outFile = dir / "sbt-openapi-codegen" / s"$objectName.scala"

// 1. make the file under cache/sbt-tapircodegen.
// 2. compare its SHA1 against cache/sbtbuildinfo-inputs
Expand All @@ -31,7 +34,7 @@ case class OpenapiCodegenTask(
def makeFile(file: File): Task[File] = {
task {
val parsed = YamlParser.parseFile(IO.readLines(inputYaml).mkString("\n"))
val lines = BasicGenerator.generateObjects(parsed.right.get).linesIterator.toSeq
val lines = BasicGenerator.generateObjects(parsed.right.get, packageName, objectName).linesIterator.toSeq
IO.writeLines(file, lines, IO.utf8)
file
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ object BasicGenerator {
val classGenerator = new ClassDefinitionGenerator()
val endpointGenerator = new EndpointGenerator()

def generateObjects(doc: OpenapiDocument) = {
def generateObjects(doc: OpenapiDocument, packagePath: String, objName: String): String = {
s"""|
|$packageStr
|package $packagePath
|
|object $objName {
|
Expand All @@ -33,10 +33,6 @@ object BasicGenerator {
|""".stripMargin
}

private[codegen] def packageStr: String = "package sttp.tapir.generated"

private[codegen] def objName = "TapirGeneratedEndpoints"

private[codegen] def imports: String =
"""import sttp.tapir._
|import sttp.tapir.json.circe._
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

lazy val root = (project in file("."))
.enablePlugins(OpenapiCodegenPlugin)
.settings(
scalaVersion := "2.12.4",
version := "0.1"
)

libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "0.17.0-M2"
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "0.17.0-M2"
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-openapi-circe-yaml" % "0.17.0-M2"

import scala.io.Source

TaskKey[Unit]("check") := {
val reference = Source.fromFile("swagger.yaml").getLines.mkString("\n")
val out = Source.fromFile("target/swagger.yaml").getLines.mkString("\n")
if (out != reference) {
sys.error("unexpected output:\n" + out + "\n\n" + (out diff reference) + "\n\n" + (reference diff out))
}
()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.3.13
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
val pluginVersion = System.getProperty("plugin.version")
if(pluginVersion == null)
throw new RuntimeException("""|
|
|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.
|
|""".stripMargin)
else addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % pluginVersion)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

object Main extends App {
/*
import sttp.tapir._
import sttp.tapir.json.circe._
import io.circe.generic.auto._
type Limit = Int
type AuthToken = String
case class BooksFromYear(genre: String, year: Int)
case class Book(title: String)
val booksListing: Endpoint[(BooksFromYear, Limit, AuthToken), String, List[Book], Any] =
endpoint
.get
.in(("books" / path[String]("genre") / path[Int]("year")).mapTo(BooksFromYear))
.in(query[Limit]("limit").description("Maximum number of books to retrieve"))
.in(header[AuthToken]("X-Auth-Token"))
.errorOut(stringBody)
.out(jsonBody[List[Book]])
*/
import sttp.tapir.generated._
import sttp.tapir.docs.openapi._
import sttp.tapir.openapi.circe.yaml._

val docs = TapirGeneratedEndpoints.generatedEndpoints.toOpenAPI("My Bookshop", "1.0")

import java.nio.file.{Paths, Files}
import java.nio.charset.StandardCharsets

Files.write(Paths.get("target/swagger.yaml"), docs.toYaml.getBytes(StandardCharsets.UTF_8))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
openapi: 3.0.1
info:
title: My Bookshop
version: '1.0'
paths:
/books/{genre}/{year}:
get:
operationId: getBooksGenreYear
parameters:
- name: genre
in: path
required: true
schema:
type: string
- name: year
in: path
required: true
schema:
type: integer
- name: limit
in: query
description: Maximum number of books to retrieve
required: true
schema:
type: integer
- name: X-Auth-Token
in: header
required: true
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Book'
default:
description: ''
content:
text/plain:
schema:
type: string
components:
schemas:
Book:
required:
- title
type: object
properties:
title:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
> clean
> run
> check
$ copy-file target/scala-2.12/src_managed/main/sbt-openapi-codegen/TapirGeneratedEndpoints.scala target/TapirGeneratedEndpoints.scala
> compile
$ newer target/TapirGeneratedEndpoints.scala target/scala-2.12/src_managed/main/sbt-openapi-codegen/TapirGeneratedEndpoints.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

lazy val root = (project in file("."))
.enablePlugins(OpenapiCodegenPlugin)
.settings(
scalaVersion := "2.12.4",
version := "0.1",
openapiPackage := "com.example.generated.apis",
openapiObject := "MyExampleEndpoints",
openapiSwaggerFile := baseDirectory.value / "example_swagger.yaml",
)

libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "0.17.0-M2"
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "0.17.0-M2"
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-openapi-circe-yaml" % "0.17.0-M2"


import scala.io.Source


TaskKey[Unit]("check") := {
val reference = Source.fromFile("example_swagger.yaml").getLines.mkString("\n")
val out = Source.fromFile("target/swagger.yaml").getLines.mkString("\n")
if (out != reference) {
sys.error("unexpected output:\n" + out + "\n\n" + (out diff reference) + "\n\n" + (reference diff out))
}
()
}
Loading

0 comments on commit ed46bc8

Please sign in to comment.