{"id":18005531,"url":"https://github.com/eikek/sbt-openapi-schema","last_synced_at":"2025-11-06T10:03:54.667Z","repository":{"id":37038329,"uuid":"178213765","full_name":"eikek/sbt-openapi-schema","owner":"eikek","description":"Generate schema sources for Scala, Java and Elm from an openapi 3.0 spec.","archived":false,"fork":false,"pushed_at":"2025-03-17T06:20:46.000Z","size":691,"stargazers_count":24,"open_issues_count":16,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-21T15:24:12.473Z","etag":null,"topics":["elm","java","openapi","openapi-codegen","openapi3","sbt-plugin","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eikek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-28T13:53:17.000Z","updated_at":"2025-03-17T06:20:48.000Z","dependencies_parsed_at":"2023-12-15T07:29:51.156Z","dependency_job_id":"5066d3b0-28fa-49e9-9f04-552f2588d653","html_url":"https://github.com/eikek/sbt-openapi-schema","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eikek%2Fsbt-openapi-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eikek%2Fsbt-openapi-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eikek%2Fsbt-openapi-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eikek%2Fsbt-openapi-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eikek","download_url":"https://codeload.github.com/eikek/sbt-openapi-schema/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245637102,"owners_count":20648092,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elm","java","openapi","openapi-codegen","openapi3","sbt-plugin","scala"],"created_at":"2024-10-30T00:20:03.866Z","updated_at":"2025-11-06T10:03:54.621Z","avatar_url":"https://github.com/eikek.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SBT OpenApi Schema Codegen\n\n[![CI](https://github.com/eikek/sbt-openapi-schema/actions/workflows/ci.yml/badge.svg)](https://github.com/eikek/sbt-openapi-schema/actions/workflows/ci.yml)\n[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat-square\u0026logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)\n[![License](https://img.shields.io/github/license/eikek/sbt-openapi-schema.svg?style=flat-square\u0026color=steelblue)](https://github.com/eikek/sbt-openapi-schema/blob/master/LICENSE.txt)\n\n\nThis is an sbt plugin to generate Scala or Elm code given an openapi\n3.x specification. Unlike other codegen tools, this focuses only on\nthe `#/components/schema` section. Also, it generates immutable\nclasses and optionally the corresponding JSON conversions.\n\n- Scala: `case class`es are generated and JSON conversion via circe.\n- Elm: records are generated and constructors for \"empty\" values. It\n  works only for objects. JSON conversion is generated using Elm's\n  default encoding support and the\n  [json-decode-pipeline](https://github.com/NoRedInk/elm-json-decode-pipeline)\n  module for decoding.\n- JSON support is optional.\n\nThe implementation is based on the\n[swagger-parser](https://github.com/swagger-api/swagger-parser)\nproject.\n\nIt is possible to customize the code generation.\n\n## Usage\n\nAdd this plugin to your build in `project/plugins.sbt`:\n\n```\naddSbtPlugin(\"com.github.eikek\" % \"sbt-openapi-schema\" % \"x.y.z\")\n```\n\nPlease check the git tags or maven central for the current version.\nThen enable the plugin in some project:\n\n```\nenablePlugins(OpenApiSchema)\n```\n\nThere are two required settings: `openapiSpec` and\n`openapiTargetLanguage`. The first defines the openapi.yml file and\nthe other is a constant from the `Language` object:\n\n```scala\nimport com.github.eikek.sbt.openapi._\n\nproject.\n  enablePlugins(OpenApiSchema).\n  settings(\n    openapiTargetLanguage := Language.Scala\n    openapiSpec := (Compile/resourceDirectory).value/\"test.yml\"\n  )\n```\n\nThe sources are automatically generated when you run `compile`. The\ntask `openapiCodegen` can be used to run the generation independent\nfrom the `compile` task.\n\n## Configuration\n\nThe configuration is specific to the target language. There exists a\nseparate configuration object for Scala and Elm.\n\nThe key `openapiScalaConfig` defines some configuration to customize\nthe code generation.\n\nFor Scala, it looks like this:\n```scala\ncase class ScalaConfig(\n    mapping: CustomMapping = CustomMapping.none,\n    json: ScalaJson = ScalaJson.none\n) {\n\n  def withJson(json: ScalaJson): ScalaConfig =\n    copy(json = json)\n\n  def addMapping(cm: CustomMapping): ScalaConfig =\n    copy(mapping = mapping.andThen(cm))\n\n  def setMapping(cm: CustomMapping): ScalaConfig =\n    copy(mapping = cm)\n}\n```\n\nBy default, no JSON support is added to the generated classes. This\ncan be changed via:\n\n```\nopenapiScalaConfig := ScalaConfig().withJson(ScalaJson.circeSemiauto)\n```\n\nThis generates the encoder and decoder using\n[circe](https://github.com/circe/circe). Note, that this plugin\ndoesn't change your `libraryDependencies` setting. So you need to add\nthe circe dependencies yourself.\n\nThe `CustomMapping` class allows to change the class names or use\ndifferent types (for example, you might want to change `LocalDate` to\n`Date`).\n\nIt looks like this:\n```scala\ntrait CustomMapping { self =\u003e\n\n  def changeType(td: TypeDef): TypeDef\n\n  def changeSource(src: SourceFile): SourceFile\n\n  def andThen(cm: CustomMapping): CustomMapping = new CustomMapping {\n    def changeType(td: TypeDef): TypeDef =\n      cm.changeType(self.changeType(td))\n\n    def changeSource(src: SourceFile): SourceFile =\n      cm.changeSource(self.changeSource(src))\n  }\n}\n```\n\nThere are convenient constructors in its companion object.\n\nIt allows to use different types via `changeType` or change the source\nfile. Here is a `build.sbt` example snippet:\n\n```scala\nimport com.github.eikek.sbt.openapi._\n\nval CirceVersion            = \"0.14.1\"\nlibraryDependencies ++= Seq(\n  \"io.circe\" %% \"circe-generic\" % CirceVersion,\n  \"io.circe\" %% \"circe-parser\"  % CirceVersion\n)\n\nopenapiSpec := (Compile/resourceDirectory).value/\"test.yml\"\nopenapiTargetLanguage := Language.Scala\nCompile/openapiScalaConfig := ScalaConfig()\n    .withJson(ScalaJson.circeSemiauto)\n    .addMapping(CustomMapping.forType({ case TypeDef(\"LocalDateTime\", _) =\u003e\n      TypeDef(\"Timestamp\", Imports(\"com.mypackage.Timestamp\"))\n    }))\n    .addMapping(CustomMapping.forName({ case s =\u003e s + \"Dto\" }))\n\nenablePlugins(OpenApiSchema)\n```\n\nIt adds circe JSON support and changes the name of all classes by\nappending the suffix \"Dto\". It also changes the type used for local\ndates to be `com.mypackage.Timestamp`.\n\n\n## Elm\n\nThere is some experimental support for generating Elm data structures\nand corresponding JSON conversion functions. When using the\n`decodePipeline` json variant, you need to install these packages:\n\n```\nelm install elm/json\nelm install NoRedInk/elm-json-decode-pipeline\n```\n\nThe default output path for elm sources is `target/elm-src`. So in\nyour `elm.json` file, add this directory to the `source-directories`\nlist along with the main source dir. It may look something like this:\n\n```json\n{\n    \"type\": \"application\",\n    \"source-directories\": [\n        \"modules/webapp/target/elm-src\",\n        \"modules/webapp/src/main/elm\"\n    ],\n    \"elm-version\": \"0.19.0\",\n    \"dependencies\": {\n        \"direct\": {\n            \"NoRedInk/elm-json-decode-pipeline\": \"1.0.0\",\n            \"elm/browser\": \"1.0.1\",\n            \"elm/core\": \"1.0.2\",\n            \"elm/html\": \"1.0.0\",\n            \"elm/json\": \"1.1.3\"\n        },\n        \"indirect\": {\n            \"elm/time\": \"1.0.0\",\n            \"elm/url\": \"1.0.0\",\n            \"elm/virtual-dom\": \"1.0.2\"\n        }\n    },\n    \"test-dependencies\": {\n        \"direct\": {},\n        \"indirect\": {}\n    }\n}\n```\n\nIt always generates type aliases for records.\n\nWhile source files for scala are added to sbt's `sourceGenerators` so\nthat they get compiled with your sources, the elm source files are not\nadded anywhere, because there is no support for Elm in sbt. However,\nin the `build.sbt` file, you can tell sbt to generate the files before\ncompiling your elm app. This can be configured to run during resource\ngeneration. Example:\n\n``` scala\n\n// Put resulting js file into the webjar location\ndef compileElm(logger: Logger, wd: File, outBase: File, artifact: String, version: String): Seq[File] = {\n  logger.info(\"Compile elm files ...\")\n  val target = outBase/\"META-INF\"/\"resources\"/\"webjars\"/artifact/version/\"my-app.js\"\n  val proc = Process(Seq(\"elm\", \"make\", \"--output\", target.toString) ++ Seq(wd/\"src\"/\"main\"/\"elm\"/\"Main.elm\").map(_.toString), Some(wd))\n  val out = proc.!!\n  logger.info(out)\n  Seq(target)\n}\n\nval webapp = project.in(file(\"webapp\")).\n  enablePlugins(OpenApiSchema).\n  settings(\n    openapiTargetLanguage := Language.Elm,\n    openapiPackage := Pkg(\"Api.Model\"),\n    openapiSpec := (Compile/resourceDirectory).value/\"openapi.yml\",\n    openapiElmConfig := ElmConfig().withJson(ElmJson.decodePipeline),\n    Compile/resourceGenerators += (Def.task {\n      openapiCodegen.value // generate api model files\n      compileElm(streams.value.log\n        , (Compile/baseDirectory).value\n        , (Compile/resourceManaged).value\n        , name.value\n        , version.value)\n    }).taskValue,\n    watchSources += Watched.WatchSource(\n      (Compile/sourceDirectory).value/\"elm\"\n        , FileFilter.globFilter(\"*.elm\")\n        , HiddenFileFilter\n    )\n  )\n```\n\nThis example assumes a `elm.json` project file in the source root.\n\n## Discriminator Support\n\nOpenAPI 3.0 enables to introduce subtyping on generated schemas by using [discriminators](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#discriminatorObject).\n\nTwo of these are currently supported only in Scala : `oneOf` and `allOf`.   \n\n#### Setup\n\nIn order to provide JSON conversion for these discriminators with Circe, we need to make use of [circe-generic-extras](https://github.com/circe/circe-generic-extras)\n\nAn example build.sbt using the plugin would look like the following:\n```scala\nimport com.github.eikek.sbt.openapi._\n\nlibraryDependencies ++= Seq(\n  \"io.circe\" %% \"circe-generic-extras\" % \"0.11.1\",\n  \"io.circe\" %% \"circe-core\" % \"0.11.1\",\n  \"io.circe\" %% \"circe-generic\" % \"0.11.1\",\n  \"io.circe\" %% \"circe-parser\" % \"0.11.1\"\n)\n\nopenapiSpec := (Compile/resourceDirectory).value/\"test.yml\"\nopenapiTargetLanguage := Language.Scala\nopenapiScalaConfig := ScalaConfig().\n  withJson(ScalaJson.circeSemiautoExtra).\n  addMapping(CustomMapping.forName({ case s =\u003e s + \"Dto\" }))\n\nenablePlugins(OpenApiSchema)\n```\n\n\n#### Handle `allOf` keywords in Scala\n\nHere is an example OpenAPI spec and the resulting Scala models with JSON conversions\n\n```yaml\ncomponents:\n  schemas:\n    Pet:\n      type: object\n      discriminator:\n        propertyName: petType\n      properties:\n        name:\n          type: string\n        petType:\n          type: string\n      required:\n      - name\n      - petType\n    Cat:  ## \"Cat\" will be used as the discriminator value\n      description: A representation of a cat\n      allOf:\n      - $ref: '#/components/schemas/Pet'\n      - type: object\n        properties:\n          huntingSkill:\n            type: string\n            description: The measured skill for hunting\n        required:\n        - huntingSkill\n    Dog:  ## \"Dog\" will be used as the discriminator value\n      description: A representation of a dog\n      allOf:\n      - $ref: '#/components/schemas/Pet'\n      - type: object\n        properties:\n          packSize:\n            type: integer\n            format: int32\n            description: the size of the pack the dog is from\n        required:\n        - packSize\n```\n\n```scala\nimport io.circe._\nimport io.circe.generic.extras.semiauto._\nimport io.circe.generic.extras.Configuration\n\nsealed trait PetDto {\n  val name: String\n}\nobject PetDto {\n  implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n\n  case class Cat (\n    huntingSkill: String, name: String\n  ) extends PetDto\n\n  case class Dog (\n    packSize: Int, name: String\n  ) extends PetDto\n\n  object Cat {\n    implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n    private implicit val jsonDecoder: Decoder[Cat] = deriveDecoder[Cat]\n    private implicit val jsonEncoder: Encoder[Cat] = deriveEncoder[Cat]\n  }\n\n  object Dog {\n    implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n    private implicit val jsonDecoder: Decoder[Dog] = deriveDecoder[Dog]\n    private implicit val jsonEncoder: Encoder[Dog] = deriveEncoder[Dog]\n  }\n\n  implicit val jsonDecoder: Decoder[PetDto] = deriveDecoder[PetDto]\n  implicit val jsonEncoder: Encoder[PetDto] = deriveEncoder[PetDto]\n}\n\n```\n\nNotes about the above example:\n- The internal schemas (\"Dog\" and \"Cat\") have private encoder/decoders so that they are only encoded and decoded as the trait interface. If you try to decode as a Dog or Cat type, the circe-generic-extras doesn't include the discriminant type\n- The mapping functionality (adding \"Dto\") is only used on the sealed trait since the discriminant type uses the name of the inner case classes (\"Dog\" and \"Cat\").\n\n#### Handle `oneOf` keywords in Scala\n\nAnother way of transform composed schemas into `sealed trait` hierarchies is to use `oneOf`.\n\n```yaml \nPet:\n  type: object\n  discriminator:\n    propertyName: petType\n  oneOf:\n    - $ref: '#/components/schemas/Cat'\n    - $ref: '#/components/schemas/Dog'\nCat:  ## \"Cat\" will be used as the discriminator value\n  description: A representation of a cat\n  properties:\n    huntingSkill:\n      type: string\n      description: The measured skill for hunting\n      enum:\n        - clueless\n        - lazy\n        - adventurous\n        - aggressive\n    name:\n      type: string\n    petType:\n      type: string\n  required:\n    - huntingSkill\n    - name\n    - petType\nDog:  ## \"Dog\" will be used as the discriminator value\n  description: A representation of a dog\n  properties:\n    packSize:\n      type: integer\n      format: int32\n      description: the size of the pack the dog is from\n      default: 0\n      minimum: 0\n    name:\n      type: string\n    petType:\n      type: string\n  required:\n    - packSize\n    - name\n    - petType\n```\n\n```scala\nimport io.circe._\nimport io.circe.generic.extras.semiauto._\nimport io.circe.generic.extras.Configuration\n\nsealed trait PetDto {\n} \nobject PetDto {\n  implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n  case class Cat (\n    huntingSkill: String, name: String, petType: String\n  ) extends PetDto\n  case class Dog (\n    packSize: Int, name: String, petType: String\n  ) extends PetDto\n  object Cat {\n    implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n    private implicit val jsonDecoder: Decoder[Cat] = deriveDecoder[Cat]\n    private implicit val jsonEncoder: Encoder[Cat] = deriveEncoder[Cat]\n  }\n  object Dog {\n    implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator(\"petType\")\n    private implicit val jsonDecoder: Decoder[Dog] = deriveDecoder[Dog]\n    private implicit val jsonEncoder: Encoder[Dog] = deriveEncoder[Dog]\n  }\n  implicit val jsonDecoder: Decoder[PetDto] = deriveDecoder[PetDto]\n  implicit val jsonEncoder: Encoder[PetDto] = deriveEncoder[PetDto]\n} \n```\n\nUnlike `allOf`, `oneOf` doesn't permit subschemas to inherit fields from their parent. This kind of relation fits well to algebraic data types encodings in Scala.\n\n## Static Documentation\n\nThe plugin can run the [swagger codegen\ntool](https://github.com/swagger-api/swagger-codegen) or\n[redoc](https://github.com/Redocly/redoc) to produce a static HTML\npage of the OpenAPI specification file.\n\nDefine which generator to use via:\n\n``` scala\nopenapiStaticGen := OpenApiDocGenerator.Redoc //or\nopenapiStaticGen := OpenApiDocGenerator.Swagger\n```\n\nNote that nodejs (the `npx` command) is required for redoc! The\ndefault is swagger.\n\nThen use the `openapiStaticDoc` task to generate the documentation\nfrom your openapi specification.\n\n\nAdditionally, there is also a task that runs [`openapi-cli\nlint`](https://redoc.ly/docs/cli/) against your specification file.\nThis also requires to have nodejs installed.\n\n\n\n## Credits\n\nFirst, thank you all who reported issues! It follows a list of\ncontributions in form of code. If you find yourself missing, please\nlet me know or open a PR.\n\n- @xela85 for adding `oneOf` keywords support (#42)\n- @mhertogs for adding support for discriminators (#8)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feikek%2Fsbt-openapi-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feikek%2Fsbt-openapi-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feikek%2Fsbt-openapi-schema/lists"}