Skip to content

TomasMikula/jing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JING: Just Import 'N' Go

Maven Central Version

Goal

Frictionless spec-first programming against OpenAPI

Sources of Friction

  • Setting up code generation

    State of the art in specification-first programming relies on first generating some code (data types, interfaces, stubs, ...) from the specification. This usually means configuring a build step to run prior to compilation, potentially with the help of a dedicated build plugin for your build tool of choice.

  • Dealing with low-level details

    ... like serialization or HTTP protocol.

These present substantial accidental coplexity, especially for the inital, explorative phase of a project.

Solution

Just Import 'N' Go 😎

Use compile-time metaprogramming to generate code from spec "on-the-fly" in the usual compile-time, without a previous codegen step. This approach requires no build setup other than adding jing to library dependencies.

Additionally, the generated endpoints are ready to use, using reasonable default choices of HTTP and JSON libraries behind the scenes.

Quick Start

  1. Add JING to library dependencies
  • sbt
    libraryDependencies += "dev.continuously.jing" %% "jing-openapi" % "0.0.2"
  • Scala CLI
    //> using dep dev.continuously.jing::jing-openapi:0.0.2
  1. Just "import" an API
val api = jing.openapi("https://petstore3.swagger.io/api/v3/openapi.json")
  1. And discover the rest from there 😉

Example

val api = jing.openapi("https://petstore3.swagger.io/api/v3/openapi.json")

api
  .paths
  .`/pet/findByStatus`
  .Get
  .as[ClientEndpoint]
  .params:
    ( status = "available" )
  .runAgainst("https://petstore3.swagger.io/api/v3")

See more in TestApp.scala.

Design Principles

  • Discoverability.

    Import an API and discover the rest from there, without prior knowledge of the API or deep knowledge of the jing library.

    👍 type-drivenness, IDE-friendliness, helpful compilation errors

    👎 chains of implicits, orphan typeclasses

  • Safety

    Make it very hard to shoot yourself in the foot.

    👍 type-safety, illegal states unrepresentable

  • Simple things simple.

    👍 shortcuts for common scenarios

  • Complex things possible.

    If needed, delegate to state-of-the-art 3rd party libraries, but provide a smooth handover of control.

    👎 low complexity ceiling

Non-goals

  • Full OpenAPI support. OpenAPI is unnecessarily bloated. jing will aim to support a reasonable, computation sympathetic subset of OpenAPI. Feel free to open a ticket if you need something that's missing.

    Nevertheless, even if your API specification uses features unsupported by JING, you should be able to use at least the parts where those features are not used. That is, JING will not reject your whole spec just because it uses an unsupported schema of an optional parameter in one of the endpoints.

Status

Successful proof of concept.

Lots of OpenAPI features are still missing.

There's lot's of space for improvement even with regards to the above principles (but feel free to call me out on violations thereof, at least I will know that you care).

Q&A

How can JING match the capabilities of standalone code generators, given the restrictions of Scala macros?

The main limitation is that a macro cannot introduce new (value or type) definitions that would be visible from outside the macro expansion. For example, JING cannot generate a class for each schema and each endpoint found in the specification, or place the generated endpoints into an object.

To stay within these constraints, code generated by JING is quite different from code produced by other generators. Basically

val api = jing.openapi("path/to/openapi.json")

produces a single value. The type of this value is a big structural type, something like this:

val api: {
  val schemas: {
    type Pet
    val Pet: {
      val schema: Schema[Pet]
      def apply(...): Value[Pet] // constructor of Pet
      def unapply(x: Value[Pet]): Some[...] // deconstructor of Pet
    }
    // ...
  }
  val paths: {
    val `/pet`: {
      val Post: HttpEndpoint[..., ...]
      val Put: HttpEndpoint[..., ...]
    }
    // ...
  }
}

In the macro-generated body of this value definition, classes can be defined and instantiated, but they won't be visible from the outside.

Does JING support feature [xyz]?

Probably not yet. Feel free to raise a ticket with a question or feature request.

About

Typesafe, spec-first APIs without codegen. Just Import 'N' Go!

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages