All Projects → ghostbuster91 → sttp-openapi-generator

ghostbuster91 / sttp-openapi-generator

Licence: Apache-2.0 license
Generate sttp client from openapi specification with ease!

Programming Languages

scala
5932 projects
shell
77523 projects

Projects that are alternatives of or similar to sttp-openapi-generator

bow-openapi
🌐 Functional HTTP client generator from an OpenAPI/Swagger specification.
Stars: ✭ 47 (+80.77%)
Mutual labels:  http-client, openapi
Martian
The HTTP abstraction library for Clojure/script, supporting Swagger, Schema, re-frame and more
Stars: ✭ 294 (+1030.77%)
Mutual labels:  http-client, openapi
cube
Common LISP Kubernetes Client
Stars: ✭ 33 (+26.92%)
Mutual labels:  http-client, openapi
OpenAPI-Swift
KKBOX Open API Swift Developer SDK for iOS/macOS/watchOS/tvOS
Stars: ✭ 13 (-50%)
Mutual labels:  openapi
reky
Reky is an opensource API development platform. It automatically visualizes API response with outstanding graphs & tables.
Stars: ✭ 22 (-15.38%)
Mutual labels:  http-client
openapi-generator
An OpenAPI document generator. ⚙️
Stars: ✭ 22 (-15.38%)
Mutual labels:  openapi
mock-json-schema
Simple utility to mock example objects based on JSON schema definitions
Stars: ✭ 23 (-11.54%)
Mutual labels:  openapi
swagger-spec
Spec for Swagger 2.0 definition
Stars: ✭ 17 (-34.62%)
Mutual labels:  openapi
cookbook
VueJS + NodeJS Evergreen Cookbook
Stars: ✭ 440 (+1592.31%)
Mutual labels:  openapi
http
A janet http client library
Stars: ✭ 22 (-15.38%)
Mutual labels:  http-client
swagger-editor-validate
This GitHub Actions validates OpenAPI (OAS) definition file using Swagger Editor.
Stars: ✭ 30 (+15.38%)
Mutual labels:  openapi
openapi-python-client
Generate modern Python clients from OpenAPI
Stars: ✭ 543 (+1988.46%)
Mutual labels:  openapi
WaterPipe
URL routing framework, requests/responses handler, and HTTP client for PHP
Stars: ✭ 24 (-7.69%)
Mutual labels:  http-client
oag
Idiomatic Go (Golang) client package generation from OpenAPI documents
Stars: ✭ 51 (+96.15%)
Mutual labels:  openapi
sanic-ext
Extended Sanic functionality
Stars: ✭ 26 (+0%)
Mutual labels:  openapi
htest
htest is a http-test package
Stars: ✭ 24 (-7.69%)
Mutual labels:  http-client
go-http-client
An enhanced http client for Golang
Stars: ✭ 48 (+84.62%)
Mutual labels:  http-client
cakephp-swagger-bake
Automatically generate OpenAPI, Swagger, and Redoc documentation from your existing CakePHP code.
Stars: ✭ 48 (+84.62%)
Mutual labels:  openapi
dynamic-cli
A Modern, user-friendly command-line HTTP client for the API testing, and if you're stuck - Search and browse StackOverflow without leaving the CLI
Stars: ✭ 151 (+480.77%)
Mutual labels:  http-client
apitte-openapi
👪 OpenAPI specification for Apitte stack
Stars: ✭ 15 (-42.31%)
Mutual labels:  openapi

sttp-openapi-generator

FOSSA Status GitHub Workflow Maven Central

This project is in a very early stage, use it at your own risk!

The generator can be used in projects with following scala versions: 2.12, 2.13 and 3.x

Why?

Why creating another openapi-generator when there is an official one? While the mentioned generator is generally a great project and serves well for many people its scala part has a few flaws in my opinion. There is no proper encoding for discriminators, neither support for other json libraries. The genereted code doesn't feel like native. These, as well as the other things, could (and probably will at some point) be implemented, but the size of the project and underlying templating engine(mustache) don't make it easier. Last but not least it is currently impossible to generate openapi code into src-managed directory (OpenAPITools/openapi-generator#6685). I think that, by extracting and focusing only on a scala related part, it can be done better.

Goals of the project

Teaser

Given following yaml:

openapi: 3.0.3
info:
  title: Entities
  version: "1.0"
paths:
  /:
    get:
      operationId: getRoot
      responses:
        "200":
          description: ""
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
components:
  schemas:
    Person:
      required:
        - name
        - age
      type: object
      properties:
        name:
          type: string
        age:
          type: integer
          minimum: 11

it will be turned into:

trait CirceCodecs extends SttpCirceApi {
  implicit lazy val personDecoder: Decoder[Person] =
    Decoder.forProduct2("name", "age")(Person.apply)
  implicit lazy val personEncoder: Encoder[Person] =
    Encoder.forProduct2("name", "age")(p => (p.name, p.age))
}
object CirceCodecs extends CirceCodecs

case class Person(name: String, age: Int)

class DefaultApi(baseUrl: String, circeCodecs: CirceCodecs = CirceCodecs) {
  import circeCodecs._

  def getRoot(): Request[Person, Any] = basicRequest
    .get(uri"$baseUrl")
    .response(
      fromMetadata(
        asJson[Person].getRight,
        ConditionalResponseAs(
          _.code == StatusCode.unsafeApply(200),
          asJson[Person].getRight
        )
      )
    )
}

Getting started

sbt-plugin

You can use sttp-openapi-codegen seamlessly with sbt via a dedicated sbt plugin. In order to use it, follow the usual convention, first add it to project/plugins.sbt:

addSbtPlugin("io.github.ghostbuster91.sttp-openapi" % "sbt-codegen-plugin" % <version>)

next, enable it for the desired modules in build.sbt:

import SttpOpenApiCodegenPlugin._
enablePlugins(SttpOpenApiCodegenPlugin)

Generator will walk through all files in input directory and generate for each one respective code into output directory. Package name based on directory structure will be preserved.

Code generation can be configured by one of the following options:

  • sttpOpenApiOutputPath - Directory for sources generated by sttp-openapi generator (default: target/scala-2.12/src_managed/)

  • sttpOpenApiInputPath - Input resources for sttp-openapi generator (default: ./resources)

  • sttpOpenApiJsonLibrary - Json library for sttp-openapi generator to use (currently only Circe)

  • sttpOpenApiHandleErrors - If true the generator will include error information in types (default: true)

For always up-to-date examples of codegen plugin usage refer to sbt tests: https://github.com/ghostbuster91/sttp-openapi-generator/tree/master/sbt-codegen-plugin/src/sbt-test/codegen/simple

mill-plugin

You can use sttp-openapi-codegen seamlessly with mill via a dedicated mill plugin.

import mill._, mill.scalalib._

import $ivy.`io.github.ghostbuster91.sttp-openapi::mill-codegen-plugin:<version>`
import io.github.ghostbuster91.sttp.client3.OpenApiCodegenScalaModule

object app extends OpenApiCodegenScalaModule {
  def scalaVersion = "2.13.2"
  def mainClass = Some("com.example.Main")
}

Additional configuration options:

/** Input resources for sttp-openapi generator
  */
def sttpOpenApiInput: T[Seq[Input]] = T {
  Seq(
    Input.dir(millSourcePath / "src" / "main" / "openapi"),
    Input.dir(millSourcePath / "openapi")
  )
}

/** Json library for sttp-openapi generator to use
  */
def sttpOpenApiJsonLibrary: T[JsonLibrary] = T(JsonLibrary.Circe: JsonLibrary)

/** If true the generator will include error information in types
  */
def sttpOpenApiHandleErrors: T[Boolean] = T(true)

/** If true the generator will render model classes only if they are
  * referenced by any of the exiting operations
  */
def sttpOpenApiMinimizeOutput: T[Boolean] = T(true)

/** Additional types mapping configuration
  */
def sttpOpenApiTypesMapping: T[TypesMapping] = T(TypesMapping())

For always up-to-date examples of codegen plugin usage refer to mill plugin integration tests: https://github.com/ghostbuster91/sttp-openapi-generator/tree/master/mill-codegen-plugin-itest

discriminators

In the openapi specification there is a notion of discriminators. These objects are used to distinguishing between polymorphic instances of some type based on a given value.

This project takes advantage of them and generates json configs accordingly.

components:
  schemas:
    Entity:
      oneOf:
        - $ref: "#/components/schemas/Person"
        - $ref: "#/components/schemas/Organization"
      discriminator:
        propertyName: name
        mapping:
          john: "#/components/schemas/Person"
          sml: "#/components/schemas/Organization"
    Person:
      required:
        - name
        - age
      type: object
      properties:
        name:
          type: string
        age:
          type: integer
    Organization:
      required:
        - name
        - size
      type: object
      properties:
        name:
          type: string
        size:
          type: integer
sealed trait Entity
case class Organization(size: Int) extends Entity()
case class Person(name: String, age: Int) extends Entity()

trait CirceCodecs extends SttpCirceApi {
  // codecs for Person and Organization omitted for readability

  implicit lazy val entityDecoder: Decoder[Entity] = new Decoder[Entity]() {
    override def apply(c: HCursor): Result[Entity] = c
      .downField("name")
      .as[String]
      .flatMap({
        case "john" => Decoder[Person].apply(c)
        case "sml"  => Decoder[Organization].apply(c)
        case other =>
          Left(DecodingFailure("Unexpected value for coproduct:" + other, Nil))
      })
  }
  implicit lazy val entityEncoder: Encoder[Entity] = new Encoder[Entity]() {
    override def apply(entity: Entity): Json = entity match {
      case person: Person => Encoder[Person].apply(person)
      case organization: Organization =>
        Encoder[Organization].apply(organization)
    }
  }
}

error encoding

In openapi error responses can be represented equally easily as success ones. That is also the case for the sttp client. If you are not a fan of error handling, you can disable that feature in generator settings.

openapi: 3.0.2
info:
  title: Entities
  version: "1.0"
paths:
  /person:
    put:
      summary: Update an existing person
      description: Update an existing person by Id
      operationId: updatePerson
      responses:
        "400":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorModel"
        "401":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorModel2"
components:
  schemas:
    ErrorModel:
      required:
        - msg
      type: object
      properties:
        msg:
          type: string
    ErrorModel2:
      required:
        - msg
      type: object
      properties:
        msg:
          type: string
sealed trait UpdatePersonGenericError
case class ErrorModel(msg: String) extends UpdatePersonGenericError()
case class ErrorModel2(msg: String) extends UpdatePersonGenericError()

class DefaultApi(baseUrl: String, circeCodecs: CirceCodecs = CirceCodecs) {
  import circeCodecs._
 
  def updatePerson(): Request[
    Either[ResponseException[UpdatePersonGenericError, CirceError], Unit],
    Any
  ] = basicRequest
    .put(uri"$baseUrl/person")
    .response(
      fromMetadata(
        asJsonEither[UpdatePersonGenericError, Unit],
        ConditionalResponseAs(
          _.code == StatusCode.unsafeApply(400),
          asJsonEither[ErrorModel, Unit]
        ),
        ConditionalResponseAs(
          _.code == StatusCode.unsafeApply(401),
          asJsonEither[ErrorModel2, Unit]
        )
      )
    )
}

open-product

In openapi specifications data models can be extended by arbitrary properties if needed. To do that one has to specify additionalProperties on particular model. At the same time on the call site special codecs need to be provided to support such types. Luckily, sttp-openapi-generator will handle that as well.

openapi: 3.0.3
info:
  title: Entities
  version: "1.0"
paths:
  /:
    get:
      operationId: getRoot
      responses:
        "200":
          description: ""
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
components:
  schemas:
    Person:
      required:
        - name
        - age
      type: object
      properties:
        name:
          type: string
        age:
          type: integer
      additionalProperties: true
trait CirceCodecs extends SttpCirceApi {
  implicit lazy val personDecoder: Decoder[Person] = new Decoder[Person]() {
    override def apply(c: HCursor): Result[Person] =
      for {
        name <- c.downField("name").as[String]
        age <- c.downField("age").as[Int]
        additionalProperties <- c.as[Map[String, Json]]
      } yield Person(
        name,
        age,
        additionalProperties.filterKeys(_ != "name").filterKeys(_ != "age")
      )
  }
  implicit lazy val personEncoder: Encoder[Person] = new Encoder[Person]() {
    override def apply(person: Person): Json = Encoder
      .forProduct2[Person, String, Int]("name", "age")(p => (p.name, p.age))
      .apply(person)
      .deepMerge(
        Encoder[Map[String, Json]].apply(person._additionalProperties)
      )
  }

}

case class Person(
    name: String,
    age: Int,
    _additionalProperties: Map[String, Json]
)

Comparison to similar projects

Apart from official openApi generator which was mentioned in the Why? section there are other similar projects.

  • Guardrail

    Guardrail can generate both client and server code. When it comes to client code generation it is similar to that project, although it supports http4s and akka-http, while this project focuses solely on sttp.

Contributing

Contributions are more than welcome. This is an early stage project which means that everything is subject to change.

See the list of issues and pick one! Or report your own.

If you are having doubts on the why or how something works, don't hesitate to ask a question.

Releasing a new version

Push a new tag to the master branch.

License

FOSSA Status

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].