{"id":18810363,"url":"https://github.com/absaoss/springdoc-openapi-scala","last_synced_at":"2026-01-10T17:30:14.676Z","repository":{"id":214308296,"uuid":"727233343","full_name":"AbsaOSS/springdoc-openapi-scala","owner":"AbsaOSS","description":"Enhancement of springdoc-openapi for Scala","archived":false,"fork":false,"pushed_at":"2024-06-14T09:01:35.000Z","size":85,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-01-06T09:39:14.674Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AbsaOSS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-04T13:06:39.000Z","updated_at":"2024-06-14T08:57:03.000Z","dependencies_parsed_at":"2023-12-27T10:24:25.572Z","dependency_job_id":"b9b5adaa-e35d-4ff6-b1ad-c09ee4722519","html_url":"https://github.com/AbsaOSS/springdoc-openapi-scala","commit_stats":null,"previous_names":["absaoss/springdoc-openapi-scala"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AbsaOSS%2Fspringdoc-openapi-scala","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AbsaOSS%2Fspringdoc-openapi-scala/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AbsaOSS%2Fspringdoc-openapi-scala/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AbsaOSS%2Fspringdoc-openapi-scala/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AbsaOSS","download_url":"https://codeload.github.com/AbsaOSS/springdoc-openapi-scala/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239744076,"owners_count":19689600,"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":[],"created_at":"2024-11-07T23:19:57.941Z","updated_at":"2026-01-10T17:30:14.632Z","avatar_url":"https://github.com/AbsaOSS.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# springdoc-openapi-scala\n\nAn enhancement to [springdoc-openapi](https://github.com/springdoc/springdoc-openapi) that adds better support for Scala.\n\n## Motivation\nScala isn't well-supported in springdoc-openapi by default, for example:\n- `case class` parameters are not recognized by default, one has to add something like `@BeanProperty` to each\n- even with `@BeanProperty`, most parameters with generic type (like `Option`) don't work correctly\n- even with `@BeanProperty`, all parameters are marked as not required in generated OpenAPI docs\n- Spring endpoints returning `Unit` are not \"No Content\" but instead show that the endpoint returns `BoxedUnit`\n\nOne option to overcome these limitations is to annotate the model with annotations provided by springdoc-openapi.\nBut even with them, `@BeanPropery` or equivalent must be added to each case class parameter.\n\nThis library aims to avoid pollution of the model by custom annotations and dependency on Spring related libraries.\n\n## Features\n- all parameters of a `case class` are automatically recognized without any custom annotations\n- all parameters of a `case class` that have a type different from `Option` are marked as required\n- Spring endpoints returning Unit are \"No Content\"\n- support for basic Scala collections (`Map`, `Seq`, `Set`, `Array`) as types of `case class` parameters\n- only top-level case classes need to be registered, child case classes are then recursively registered\n- support for Scala `Enumeration` where simple `Value` constructor is used (without `name`)\n- support for sum ADTs (`sealed trait` and `sealed abstract class`) with optional discriminator\n\n## Usage\n\nspringdoc-openapi-scala supports two major versions of springdoc-openapi: 1.x and 2.x.\n\n### Provided dependencies\nThe library has springdoc-openapi as a provided dependency, \nthus users of the library have to include that dependency in their projects:\n- for springdoc-openapi 1.x versions `1.6.7` up to `1.7.0` (including) of \n`\"org.springdoc\" % \"springdoc-openapi-webmvc-core\"` are supported\n- for springdoc-openapi 2.x versions `2.0.0` up to `2.3.0` (including) of\n`\"org.springdoc\" % \"springdoc-openapi-starter-webmvc-api\"` are supported\n\n### Add library dependency to SBT/Maven\n#### SBT\nIf you want to use the library with springdoc-openapi 1.x, add:\n```sbt\nlibraryDependencies ++= Seq(\"za.co.absa\" %% \"springdoc-openapi-scala-1\" % VERSION)\n```\nif with springdoc-openapi 2.x, add:\n```sbt\nlibraryDependencies ++= Seq(\"za.co.absa\" %% \"springdoc-openapi-scala-2\" % VERSION)\n```\n\n#### Maven\nIf you want to use the library with springdoc-openapi 1.x, add:\n```xml\n\u003cdependency\u003e\n   \u003cgroupId\u003eza.co.absa\u003c/groupId\u003e\n   \u003cartifactId\u003espringdoc-openapi-scala-1_${scala_binary_version}\u003c/artifactId\u003e\n   \u003cversion\u003e${version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\nif with springdoc-openapi 2.x, add:\n```xml\n\u003cdependency\u003e\n   \u003cgroupId\u003eza.co.absa\u003c/groupId\u003e\n   \u003cartifactId\u003espringdoc-openapi-scala-2_${scala_binary_version}\u003c/artifactId\u003e\n   \u003cversion\u003e${version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nwhere `scala_binary_version` is either `2.12` or `2.13`.\n\n### Create custom OpenAPI Spring Configuration\nFor springdoc-openapi 1.x\n```scala\nimport io.swagger.v3.oas.models.OpenAPI\nimport io.swagger.v3.oas.models.info.Info\nimport org.springdoc.core.customizers.OpenApiCustomiser\nimport org.springframework.context.annotation.{Bean, Configuration}\n\nimport za.co.absa.springdocopenapiscala.{Bundle, OpenAPIModelRegistration}\n\n@Configuration\nclass OpenAPIConfiguration {\n\n  private val springDocOpenAPIScalaBundle = new Bundle(\n    Seq((openAPI: OpenAPI) =\u003e\n      openAPI.setInfo(\n        new Info()\n          .title(\"Example API with springdoc-openapi v1.x\")\n          .version(\"1.0.0\")\n      )\n    )\n  )\n\n  @Bean\n  def openAPICustomizer: OpenApiCustomiser = springDocOpenAPIScalaBundle.customizer\n\n  @Bean\n  def openAPIModelRegistration: OpenAPIModelRegistration = springDocOpenAPIScalaBundle.modelRegistration\n\n}\n```\n\nFor springdoc-openapi 2.x\n```scala\nimport io.swagger.v3.oas.models.OpenAPI\nimport io.swagger.v3.oas.models.info.Info\nimport org.springdoc.core.customizers.OpenApiCustomizer\nimport org.springframework.context.annotation.{Bean, Configuration}\n\nimport za.co.absa.springdocopenapiscala.{Bundle, OpenAPIModelRegistration}\n\n@Configuration\nclass OpenAPIConfiguration {\n\n  private val springDocOpenAPIScalaBundle = new Bundle(\n    Seq((openAPI: OpenAPI) =\u003e\n      openAPI.setInfo(\n        new Info()\n          .title(\"Example API with springdoc-openapi v2.x\")\n          .version(\"1.0.0\")\n      )\n    )\n  )\n\n  @Bean\n  def openAPICustomizer: OpenApiCustomizer = springDocOpenAPIScalaBundle.customizer\n\n  @Bean\n  def openAPIModelRegistration: OpenAPIModelRegistration = springDocOpenAPIScalaBundle.modelRegistration\n\n}\n```\n\n### Register top-level model case classes (for example in Controller)\nExample model:\n```scala\ncase class ExampleModelRequest(a: Int, b: String, c: Option[Int])\n\ncase class ExampleModelResponse(d: Seq[Int], e: Boolean)\n```\n\ncan be registered for example in `Controller`:\n\n```scala\n@RestController\n@RequestMapping(\n  value = Array(\"/api/v1/example\")\n)\nclass ExampleController @Autowired()(openAPIModelRegistration: OpenAPIModelRegistration) {\n\n  openAPIModelRegistration.register[ExampleModelRequest]()\n  openAPIModelRegistration.register[ExampleModelResponse]()\n\n  @PostMapping(\n    value = Array(\"/some-endpoint\"),\n    produces = Array(MediaType.APPLICATION_JSON_VALUE)\n  )\n  def someEndpoint(@RequestBody body: ExampleModelRequest): CompletableFuture[ExampleModelResponse] = {\n    ...\n  }\n  \n}\n```\n\n### Adding support for custom types\nTo add support for custom types (or overwrite handling of any type supported by the library) \none can create a custom `ExtraTypesHandler` and provide it when creating a `Bundle`.\n\nThere are multiple ways to do so, the simplest is to use `ExtraTypesHandling.simpleMapping`, for example:\n```scala\nOpenAPIModelRegistration.ExtraTypesHandling.simpleMapping {\n  case t if t =:= typeOf[JsValue] =\u003e\n    val schema = new Schema\n    schema.setType(\"string\")\n    schema.setFormat(\"json\")\n    schema\n}\n```\nThis `ExtraTypesHandler` handles `JsValue` by mapping it to simple OpenAPI `string` type with `json` format.\n\nBut `ExtraTypesHandler` can also be much more powerful, for example: \n```scala\ncase class CustomClassComplexChild(a: Option[Int])\n\nclass CustomClass(val complexChild: CustomClassComplexChild) {\n  // these won't be included\n  val meaningOfLife: Int = 42\n  val alphabetHead: String = \"abc\"\n}\n\n...\n\nval extraTypesHandler: ExtraTypesHandler = (tpe: Type) =\u003e\n  tpe match {\n    case t if t =:= typeOf[CustomClass] =\u003e\n      val childTypesToBeResolvedByTheLibrary = Set(typeOf[CustomClassComplexChild])\n      \n      val handleFn: HandleFn = (resolvedChildTypes, context) =\u003e {\n        val name = \"CustomClass\"\n        val customClassComplexChildResolvedSchema = resolvedChildTypes(typeOf[CustomClassComplexChild])\n        val schema = new Schema\n        schema.addProperty(\"complexChild\", customClassComplexChildResolvedSchema)\n        context.components.addSchemas(name, schema)\n        val schemaReference = new Schema\n        schemaReference.set$ref(s\"#/components/schemas/$name\")\n        schemaReference\n      }\n      \n      (childTypesToBeResolvedByTheLibrary, handleFn)\n  }\n```\nThis `ExtraTypesHandler` handles `CustomClass`. \n`CustomClass` uses `CustomClassComplexChild`, \nso the handler requests the library to resolve its type (`childTypesToBeResolvedByTheLibrary`).\nThis resolved type is available as input to `HandleFn`.\nThen, in `handleFn`, the handler creates a `Schema` object for `CustomClass`, \nadds it to `Components` so that it can be referenced by name `CustomClass`,\nand returns reference to that object.\n\n### Registration configuration\nIt is possible to further customize registration by providing custom `RegistrationConfig` to `OpenAPIModelRegistration`.\n\n#### Example\n```scala\nval components = ...\nval registration = OpenAPIModelRegistration(\n  components,\n  config = RegistrationConfig(\n    OpenAPIModelRegistration.RegistrationConfig(\n      sumADTsShape =\n         // default values apply for discriminatorPropertyNameFn, addDiscriminatorPropertyOnlyToDirectChildren\n         OpenAPIModelRegistration.RegistrationConfig.SumADTsShape.WithDiscriminator()\n    )\n  )\n)\n```\n\n#### sumADTsShape\nThis config property sets how sum ADTs are registered. It has two possible values:\n- `RegistrationConfig.SumADTsShape.WithoutDiscriminator` - default option, doesn't add discriminators\n- `RegistrationConfig.SumADTsShape.WithDiscriminator(discriminatorPropertyNameFn, addDiscriminatorPropertyOnlyToDirectChildren)` - \n   adds discriminator to sealed types schema,\n   and also adds discriminator to sum ADTs elements properties; discriminator property name is customizable by `discriminatorPropertyNameFn`,\n   by default it takes sealed type name, converts its first letter to lower case, and adds `\"Type\"` suffix,\n   for example if sealed type name is `Expression`, the property name is `expressionType`;\n   if `addDiscriminatorPropertyOnlyToDirectChildren` is `false`, discriminator property is added to all children,\n   so for example in `ADT = A | B | C; B = D | E` discriminator of `ADT` would be added to `A`, `C`, `D`, `E`\n   (`D` and `E` would have discriminator of `B` in addition to that) \n   while with  `addDiscriminatorPropertyOnlyToDirectChildren` set to `true` (default) \n   it would be added only to `A` and `C`\n\n## Examples\n\n### Simple example for springdoc-openapi-scala-1\nCan be found in this repo: [link](examples/springdoc-openapi-scala-1/simple). It generates the following OpenAPI JSON doc:\n```json\n{\n  \"openapi\": \"3.0.1\",\n  \"info\": {\n    \"title\": \"Example API with springdoc-openapi v1.x\",\n    \"version\": \"1.0.0\"\n  },\n  \"servers\": [\n    {\n      \"url\": \"http://localhost:8080\",\n      \"description\": \"Generated server url\"\n    }\n  ],\n  \"paths\": {\n    \"/api/v1/example/some-endpoint\": {\n      \"post\": {\n        \"tags\": [\n          \"example-controller\"\n        ],\n        \"operationId\": \"someEndpoint\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/ExampleModelRequest\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ExampleModelResponse\"\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"IntLiteral\": {\n        \"required\": [\n          \"value\"\n        ],\n        \"properties\": {\n          \"value\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"StringLiteral\": {\n        \"required\": [\n          \"value\"\n        ],\n        \"properties\": {\n          \"value\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"Literal\": {\n        \"oneOf\": [\n          {\n            \"$ref\": \"#/components/schemas/IntLiteral\"\n          },\n          {\n            \"$ref\": \"#/components/schemas/StringLiteral\"\n          }\n        ]\n      },\n      \"Something\": {},\n      \"Expression\": {\n        \"oneOf\": [\n          {\n            \"$ref\": \"#/components/schemas/Literal\"\n          },\n          {\n            \"$ref\": \"#/components/schemas/Something\"\n          }\n        ]\n      },\n      \"ExampleModelRequest\": {\n        \"required\": [\n          \"a\",\n          \"b\",\n          \"d\",\n          \"e\"\n        ],\n        \"properties\": {\n          \"a\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"b\": {\n            \"type\": \"string\"\n          },\n          \"c\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"d\": {\n            \"type\": \"string\",\n            \"format\": \"json\"\n          },\n          \"e\": {\n            \"$ref\": \"#/components/schemas/Expression\"\n          },\n          \"f\": {\n            \"$ref\": \"#/components/schemas/Expression\"\n          }\n        }\n      },\n      \"ExampleModelResponse\": {\n        \"required\": [\n          \"d\",\n          \"e\"\n        ],\n        \"properties\": {\n          \"d\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            }\n          },\n          \"e\": {\n            \"type\": \"boolean\"\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n### Simple example for springdoc-openapi-scala-2\nCan be found in this repo: [link](examples/springdoc-openapi-scala-2/simple). It generates the following OpenAPI JSON doc:\n```json\n{\n  \"openapi\": \"3.0.1\",\n  \"info\": {\n    \"title\": \"Example API with springdoc-openapi v2.x\",\n    \"version\": \"1.0.0\"\n  },\n  \"servers\": [\n    {\n      \"url\": \"http://localhost:8080\",\n      \"description\": \"Generated server url\"\n    }\n  ],\n  \"paths\": {\n    \"/api/v1/example/some-endpoint\": {\n      \"post\": {\n        \"tags\": [\n          \"example-controller\"\n        ],\n        \"operationId\": \"someEndpoint\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/ExampleModelRequest\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ExampleModelResponse\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/api/v1/example/empty-endpoint\": {\n      \"post\": {\n        \"tags\": [\n          \"example-controller\"\n        ],\n        \"operationId\": \"emptyEndpoint\",\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"ExampleModelRequest\": {\n        \"required\": [\n          \"a\",\n          \"b\",\n          \"d\",\n          \"e\"\n        ],\n        \"properties\": {\n          \"a\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"b\": {\n            \"type\": \"string\"\n          },\n          \"c\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"d\": {\n            \"type\": \"string\",\n            \"enum\": [\n              \"OptionC\",\n              \"OptionB\",\n              \"OptionA\"\n            ]\n          },\n          \"e\": {\n            \"type\": \"string\",\n            \"format\": \"json\"\n          }\n        }\n      },\n      \"ExampleModelResponse\": {\n        \"required\": [\n          \"d\",\n          \"e\"\n        ],\n        \"properties\": {\n          \"d\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            }\n          },\n          \"e\": {\n            \"type\": \"boolean\"\n          }\n        }\n      }\n    }\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsaoss%2Fspringdoc-openapi-scala","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabsaoss%2Fspringdoc-openapi-scala","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsaoss%2Fspringdoc-openapi-scala/lists"}