From d16e85eb8bb5aae1cb7e670efc251adb4e55a237 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Mon, 21 Jul 2025 19:01:27 +0200 Subject: [PATCH 01/12] Fix String/Boolean serialization (serialize to Any). Fix optional/mandatory specification for all types --- README.md | 2 +- .../springdoc-openapi-scala-2/simple/build.sbt | 2 +- .../OpenAPIScalaCustomizer.scala | 16 ++++++++++++++++ project/Dependencies.scala | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index facccc9..78b59ca 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The library has springdoc-openapi as a provided dependency, thus users of the library have to include that dependency in their projects: - for springdoc-openapi 1.x versions `1.6.7` up to `1.7.0` (including) of `"org.springdoc" % "springdoc-openapi-webmvc-core"` are supported -- for springdoc-openapi 2.x versions `2.0.0` up to `2.3.0` (including) of +- for springdoc-openapi 2.x versions `2.0.0` up to `2.8.9` (including) of `"org.springdoc" % "springdoc-openapi-starter-webmvc-api"` are supported ### Add library dependency to SBT/Maven diff --git a/examples/springdoc-openapi-scala-2/simple/build.sbt b/examples/springdoc-openapi-scala-2/simple/build.sbt index 34b64b1..35184df 100644 --- a/examples/springdoc-openapi-scala-2/simple/build.sbt +++ b/examples/springdoc-openapi-scala-2/simple/build.sbt @@ -22,7 +22,7 @@ lazy val root = (project in file(".")) .settings( libraryDependencies ++= Seq( "za.co.absa" %% "springdoc-openapi-scala-2" % `springdoc-openapi-scala-2-version`, - "org.springdoc" % "springdoc-openapi-starter-webmvc-api" % "2.3.0", + "org.springdoc" % "springdoc-openapi-starter-webmvc-api" % "2.8.9", "org.springframework.boot" % "spring-boot-starter-web" % "3.2.0", "org.playframework" %% "play-json" % "3.0.1" ), diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala index 84a6615..6f18e03 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala @@ -16,6 +16,7 @@ package za.co.absa.springdocopenapiscala +import io.swagger.v3.core.util.Json import io.swagger.v3.oas.models.{Components, OpenAPI} import za.co.absa.springdocopenapiscala.SpringdocOpenAPIVersionSpecificTypes.OpenApiCustomizer @@ -29,6 +30,21 @@ class OpenAPIScalaCustomizer(components: Components) extends OpenApiCustomizer { openAPIOutOfSync.setComponents(components) fixResponsesReturningUnit(openAPIOutOfSync) + + val jsonRepresentation = Json.pretty(openAPIOutOfSync) + + // Deserialize the JSON string back into a new OpenAPI object + val newOpenAPI = Json.mapper().readValue(jsonRepresentation, classOf[OpenAPI]) + + // Replace the root by copying properties from the new OpenAPI object + openAPIOutOfSync.setComponents(newOpenAPI.getComponents) + openAPIOutOfSync.setPaths(newOpenAPI.getPaths) + openAPIOutOfSync.setInfo(newOpenAPI.getInfo) + openAPIOutOfSync.setServers(newOpenAPI.getServers) + openAPIOutOfSync.setSecurity(newOpenAPI.getSecurity) + openAPIOutOfSync.setTags(newOpenAPI.getTags) + openAPIOutOfSync.setExternalDocs(newOpenAPI.getExternalDocs) + openAPIOutOfSync.setExtensions(newOpenAPI.getExtensions) } private def fixResponsesReturningUnit(openAPI: OpenAPI): Unit = { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 79854e3..d6255cb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { def springdocOpenapi(majorVersion: Int): String = majorVersion match { case 1 => "[1.6.7,1.7.0]" - case 2 => "[2.0.0,2.3.0]" + case 2 => "[2.0.0,2.8.9]" } } From f61e9367de06c7386de5afbe005b0964853acdf6 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Tue, 22 Jul 2025 11:25:18 +0200 Subject: [PATCH 02/12] Test no longer applicable --- .../OpenAPIScalaCustomizerSpec.scala | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala index 66ed70c..c18fccc 100644 --- a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala +++ b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala @@ -56,17 +56,6 @@ class OpenAPIScalaCustomizerSpec extends AnyFlatSpec { behavior of "customise" - it should "set `components` of its argument OpenAPI object to one injected via DI to the class" in { - val components = new Components().addSchemas("a", new Schema) - val openAPIScalaCustomizer = new OpenAPIScalaCustomizer(components) - - val openAPI = initializeOpenAPI - - openAPIScalaCustomizer.customise(openAPI) - - assert(openAPI.getComponents === components) - } - it should "convert all responses returning Unit (BoxedUnit reference) to empty response" in { val components = new Components() val openAPIScalaCustomizer = new OpenAPIScalaCustomizer(components) From 28aecd4826c5c9afc39e42c223ad2ef0f1ef38c7 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Tue, 22 Jul 2025 21:43:30 +0200 Subject: [PATCH 03/12] Reassign only components property --- .../springdocopenapiscala/OpenAPIScalaCustomizer.scala | 7 ------- 1 file changed, 7 deletions(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala index 6f18e03..d04cf33 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala @@ -38,13 +38,6 @@ class OpenAPIScalaCustomizer(components: Components) extends OpenApiCustomizer { // Replace the root by copying properties from the new OpenAPI object openAPIOutOfSync.setComponents(newOpenAPI.getComponents) - openAPIOutOfSync.setPaths(newOpenAPI.getPaths) - openAPIOutOfSync.setInfo(newOpenAPI.getInfo) - openAPIOutOfSync.setServers(newOpenAPI.getServers) - openAPIOutOfSync.setSecurity(newOpenAPI.getSecurity) - openAPIOutOfSync.setTags(newOpenAPI.getTags) - openAPIOutOfSync.setExternalDocs(newOpenAPI.getExternalDocs) - openAPIOutOfSync.setExtensions(newOpenAPI.getExtensions) } private def fixResponsesReturningUnit(openAPI: OpenAPI): Unit = { From d6b6ba51792e35ad39a85c226c166371e10ff0f7 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Tue, 22 Jul 2025 22:41:44 +0200 Subject: [PATCH 04/12] Rework --- .../OpenAPIScalaCustomizer.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala index d04cf33..f7c578c 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala @@ -25,19 +25,18 @@ import scala.collection.JavaConverters._ class OpenAPIScalaCustomizer(components: Components) extends OpenApiCustomizer { override def customise(openAPIOutOfSync: OpenAPI): Unit = { - // this is needed as for some reason springdoc-openapi cache the `OpenAPI` at the beginning - // and newly added `Components` are not taken into account on JSON/YAML generation - openAPIOutOfSync.setComponents(components) + // Serialize the customized Components object to a JSON string. + val jsonRepresentation = Json.pretty(components) - fixResponsesReturningUnit(openAPIOutOfSync) - - val jsonRepresentation = Json.pretty(openAPIOutOfSync) + // Deserialize the JSON string back into a new Components object to iron out any issues. + val newComponents = Json.mapper().readValue(jsonRepresentation, classOf[Components]) - // Deserialize the JSON string back into a new OpenAPI object - val newOpenAPI = Json.mapper().readValue(jsonRepresentation, classOf[OpenAPI]) + // Finally replace the Components object in the OpenAPI instance. + // This is needed as for some reason springdoc-openapi cache the `OpenAPI` at the beginning + // and newly added `Components` are not taken into account on JSON/YAML generation. + openAPIOutOfSync.setComponents(newComponents) - // Replace the root by copying properties from the new OpenAPI object - openAPIOutOfSync.setComponents(newOpenAPI.getComponents) + fixResponsesReturningUnit(openAPIOutOfSync) } private def fixResponsesReturningUnit(openAPI: OpenAPI): Unit = { From ec9baf51917eecc22c1d32cd6883740d908fc739 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 17:22:44 +0200 Subject: [PATCH 05/12] Further rework --- .../OpenAPIModelRegistration.scala | 102 +++++++++++------- .../OpenAPIScalaCustomizer.scala | 10 +- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala index ccbfb8d..9ae7980 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala @@ -17,7 +17,7 @@ package za.co.absa.springdocopenapiscala import io.swagger.v3.oas.models.Components -import io.swagger.v3.oas.models.media.{Discriminator, Schema} +import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Discriminator, IntegerSchema, NumberSchema, ObjectSchema, Schema, StringSchema, UUIDSchema} import java.time.{Instant, LocalDate, LocalDateTime, LocalTime, ZonedDateTime} import java.util.UUID @@ -27,6 +27,7 @@ import scala.reflect.runtime.universe._ import OpenAPIModelRegistration._ import java.sql.Timestamp +import scala.collection.Seq class OpenAPIModelRegistration( components: Components, @@ -69,8 +70,6 @@ class OpenAPIModelRegistration( handleType(tpe) } - private case class OpenAPISimpleType(tpe: String, format: Option[String] = None) - @tailrec private def handleType(tpe: Type): Schema[_] = { if (extraTypesHandler.isDefinedAt(tpe)) handleExtraTypes(tpe) @@ -96,8 +95,7 @@ class OpenAPIModelRegistration( private def handleCaseType(tpe: Type): Schema[_] = { val name = tpe.typeSymbol.name.toString.trim - val schema = new Schema - schema.setType("object") + val schema = new ObjectSchema val fields = tpe.decls.collect { case field: TermSymbol if field.isVal => field } @@ -114,17 +112,15 @@ class OpenAPIModelRegistration( private def handleMap(keyType: Type, valueType: Type): Schema[_] = keyType match { case _ if keyType <:< typeOf[String] => - val schema = new Schema - schema.setType("object") + val schema = new ObjectSchema schema.setAdditionalProperties(handleType(valueType)) schema case _ => throw new IllegalArgumentException("In OpenAPI 3.0.x Map must have String key type.") } private def handleSeqLike(tpe: Type): Schema[_] = { - val schema = new Schema + val schema = new ArraySchema val innerSchema = handleType(tpe.typeArgs.head) - schema.setType("array") schema.setItems(innerSchema) schema } @@ -139,8 +135,7 @@ class OpenAPIModelRegistration( val enumValues = parentObjectType.members.filter(isSymbolEnumerationValue) val enumValuesAsStrings = enumValues.map(_.name.toString.trim) - val schema = new Schema[String] - schema.setType("string") + val schema = new StringSchema schema.setEnum(enumValuesAsStrings.toList.asJava) schema } @@ -197,7 +192,7 @@ class OpenAPIModelRegistration( // - case objects = registered as reference // - sealed trait/abstract class = registered as reference val childrenRefs = children.map(s => (new Schema).$ref(s.name.toString.trim)).toSeq - val schema = new Schema + val schema = new ObjectSchema schema.setOneOf(childrenRefs.asJava) val schemaRef = registerAsReference(name, schema) children.map(_.asType.toType).foreach(handleType) @@ -223,34 +218,61 @@ class OpenAPIModelRegistration( } } - private def handleSimpleType(tpe: Type): Schema[_] = { - val schema = new Schema - val OpenAPISimpleType(terminalTpe, format) = getOpenAPISimpleType(tpe) - schema.setType(terminalTpe) - format.foreach(f => schema.setFormat(f)) - schema - } - - private def getOpenAPISimpleType(tpe: Type): OpenAPISimpleType = tpe.dealias match { - case t if t =:= typeOf[Byte] => OpenAPISimpleType("integer", Some("int32")) - case t if t =:= typeOf[Short] => OpenAPISimpleType("integer", Some("int32")) - case t if t =:= typeOf[Int] => OpenAPISimpleType("integer", Some("int32")) - case t if t =:= typeOf[Long] => OpenAPISimpleType("integer", Some("int64")) - case t if t =:= typeOf[Float] => OpenAPISimpleType("number", Some("float")) - case t if t =:= typeOf[Double] => OpenAPISimpleType("number", Some("double")) - case t if t =:= typeOf[Char] => OpenAPISimpleType("string") - case t if t =:= typeOf[String] => OpenAPISimpleType("string") - case t if t =:= typeOf[UUID] => OpenAPISimpleType("string", Some("uuid")) - case t if t =:= typeOf[Boolean] => OpenAPISimpleType("boolean") - case t if t =:= typeOf[Unit] => OpenAPISimpleType("null") - case t if t =:= typeOf[ZonedDateTime] => OpenAPISimpleType("string", Some("date-time")) - case t if t =:= typeOf[Instant] => OpenAPISimpleType("string", Some("date-time")) - case t if t =:= typeOf[LocalDateTime] => OpenAPISimpleType("string", Some("date-time")) - case t if t =:= typeOf[LocalDate] => OpenAPISimpleType("string", Some("date")) - case t if t =:= typeOf[LocalTime] => OpenAPISimpleType("string", Some("time")) - case t if t =:= typeOf[Timestamp] => OpenAPISimpleType("string", Some("date-time")) - case t if t =:= typeOf[BigDecimal] => OpenAPISimpleType("number") - case t if t =:= typeOf[BigInt] => OpenAPISimpleType("integer") + private def handleSimpleType(tpe: Type): Schema[_] = tpe.dealias match { + case t if t =:= typeOf[Byte] => + val s = new IntegerSchema() + s.setFormat("int32") + s + case t if t =:= typeOf[Short] => + val s = new IntegerSchema() + s.setFormat("int32") + s + case t if t =:= typeOf[Int] => + val s = new IntegerSchema() + s.setFormat("int32") + s + case t if t =:= typeOf[Long] => + val s = new IntegerSchema() + s.setFormat("int64") + s + case t if t =:= typeOf[Float] => + val s = new NumberSchema() + s.setFormat("float") + s + case t if t =:= typeOf[Double] => + val s = new NumberSchema() + s.setFormat("double") + s + case t if t =:= typeOf[Char] => + new StringSchema() + case t if t =:= typeOf[String] => + new StringSchema() + case t if t =:= typeOf[UUID] => + val s = new UUIDSchema() + s.setFormat("uuid") + s + case t if t =:= typeOf[Boolean] => + new BooleanSchema() + case t if t =:= typeOf[Unit] => + val s = new Schema[Unit]() + s.setType("null") + s + case t if t =:= typeOf[ZonedDateTime] || t =:= typeOf[Instant] || t =:= typeOf[LocalDateTime] || t =:= typeOf[Timestamp] => + val s = new StringSchema() + s.setFormat("date-time") + s + case t if t =:= typeOf[LocalDate] => + val s = new StringSchema() + s.setFormat("date") + s + case t if t =:= typeOf[LocalTime] => + val s = new StringSchema() + s.setFormat("time") + s + case t if t =:= typeOf[BigDecimal] => + new NumberSchema() + case t if t =:= typeOf[BigInt] => + new IntegerSchema() } private def registerAsReference(name: String, schema: Schema[_]): Schema[_] = { diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala index f7c578c..97a090e 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala @@ -16,7 +16,6 @@ package za.co.absa.springdocopenapiscala -import io.swagger.v3.core.util.Json import io.swagger.v3.oas.models.{Components, OpenAPI} import za.co.absa.springdocopenapiscala.SpringdocOpenAPIVersionSpecificTypes.OpenApiCustomizer @@ -25,16 +24,9 @@ import scala.collection.JavaConverters._ class OpenAPIScalaCustomizer(components: Components) extends OpenApiCustomizer { override def customise(openAPIOutOfSync: OpenAPI): Unit = { - // Serialize the customized Components object to a JSON string. - val jsonRepresentation = Json.pretty(components) - - // Deserialize the JSON string back into a new Components object to iron out any issues. - val newComponents = Json.mapper().readValue(jsonRepresentation, classOf[Components]) - - // Finally replace the Components object in the OpenAPI instance. // This is needed as for some reason springdoc-openapi cache the `OpenAPI` at the beginning // and newly added `Components` are not taken into account on JSON/YAML generation. - openAPIOutOfSync.setComponents(newComponents) + openAPIOutOfSync.setComponents(components) fixResponsesReturningUnit(openAPIOutOfSync) } From b75aad6c61f7c00572efdbf2ea7b794495c4cdf0 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 17:26:17 +0200 Subject: [PATCH 06/12] Restore unit test --- .../OpenAPIScalaCustomizerSpec.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala index c18fccc..66ed70c 100644 --- a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala +++ b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizerSpec.scala @@ -56,6 +56,17 @@ class OpenAPIScalaCustomizerSpec extends AnyFlatSpec { behavior of "customise" + it should "set `components` of its argument OpenAPI object to one injected via DI to the class" in { + val components = new Components().addSchemas("a", new Schema) + val openAPIScalaCustomizer = new OpenAPIScalaCustomizer(components) + + val openAPI = initializeOpenAPI + + openAPIScalaCustomizer.customise(openAPI) + + assert(openAPI.getComponents === components) + } + it should "convert all responses returning Unit (BoxedUnit reference) to empty response" in { val components = new Components() val openAPIScalaCustomizer = new OpenAPIScalaCustomizer(components) From 8392ebaeecd0ced20a4bef332631ca65f80814fd Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 17:29:08 +0200 Subject: [PATCH 07/12] wip --- .../absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala index 97a090e..84a6615 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIScalaCustomizer.scala @@ -24,8 +24,8 @@ import scala.collection.JavaConverters._ class OpenAPIScalaCustomizer(components: Components) extends OpenApiCustomizer { override def customise(openAPIOutOfSync: OpenAPI): Unit = { - // This is needed as for some reason springdoc-openapi cache the `OpenAPI` at the beginning - // and newly added `Components` are not taken into account on JSON/YAML generation. + // this is needed as for some reason springdoc-openapi cache the `OpenAPI` at the beginning + // and newly added `Components` are not taken into account on JSON/YAML generation openAPIOutOfSync.setComponents(components) fixResponsesReturningUnit(openAPIOutOfSync) From c4102d381757d4f64264ebd21bb5098045e1ee99 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 19:07:55 +0200 Subject: [PATCH 08/12] Fix BigInt --- .../absa/springdocopenapiscala/OpenAPIModelRegistration.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala index 9ae7980..9477129 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala @@ -272,7 +272,9 @@ class OpenAPIModelRegistration( case t if t =:= typeOf[BigDecimal] => new NumberSchema() case t if t =:= typeOf[BigInt] => - new IntegerSchema() + val s = new IntegerSchema() + s.setFormat(null) + s } private def registerAsReference(name: String, schema: Schema[_]): Schema[_] = { From 32fbfb7c392fee616498d81b75829a0ae317df53 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 22:14:35 +0200 Subject: [PATCH 09/12] Fix Maps test --- .../springdocopenapiscala/OpenAPIModelRegistrationSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistrationSpec.scala b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistrationSpec.scala index 9ec1145..a88fb43 100644 --- a/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistrationSpec.scala +++ b/library/src/test/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistrationSpec.scala @@ -17,7 +17,7 @@ package za.co.absa.springdocopenapiscala import io.swagger.v3.oas.models.Components -import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.{IntegerSchema, Schema} import org.scalatest import org.scalatest.flatspec.AnyFlatSpec @@ -281,7 +281,7 @@ class OpenAPIModelRegistrationSpec extends AnyFlatSpec { assertAdditionalPropertiesAreAsExpected( actualSchemas, "Maps.a", - (new Schema).`type`("integer").format("int32") + new IntegerSchema() ) assertTypeAndFormatAreAsExpected(actualSchemas, "Maps.b", "object") assertAdditionalPropertiesAreAsExpected( From 6a6ebe34b7b4033b11ecefcf407d9c41628a068c Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Wed, 23 Jul 2025 22:24:43 +0200 Subject: [PATCH 10/12] Mad code --- .../absa/springdocopenapiscala/OpenAPIModelRegistration.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala index 9477129..50fbd59 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala @@ -192,7 +192,7 @@ class OpenAPIModelRegistration( // - case objects = registered as reference // - sealed trait/abstract class = registered as reference val childrenRefs = children.map(s => (new Schema).$ref(s.name.toString.trim)).toSeq - val schema = new ObjectSchema + val schema = new Schema schema.setOneOf(childrenRefs.asJava) val schemaRef = registerAsReference(name, schema) children.map(_.asType.toType).foreach(handleType) From d956e436df833edda7eaca158d6285d05c063819 Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Thu, 24 Jul 2025 13:20:30 +0200 Subject: [PATCH 11/12] Minor refactoring --- .../OpenAPIModelRegistration.scala | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala index 50fbd59..997b8dc 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala @@ -70,6 +70,8 @@ class OpenAPIModelRegistration( handleType(tpe) } + private case class OpenAPISimpleType(schema: Schema[_], format: Option[String] = None, _type: Option[String] = None) + @tailrec private def handleType(tpe: Type): Schema[_] = { if (extraTypesHandler.isDefinedAt(tpe)) handleExtraTypes(tpe) @@ -218,63 +220,40 @@ class OpenAPIModelRegistration( } } - private def handleSimpleType(tpe: Type): Schema[_] = tpe.dealias match { - case t if t =:= typeOf[Byte] => - val s = new IntegerSchema() - s.setFormat("int32") - s - case t if t =:= typeOf[Short] => - val s = new IntegerSchema() - s.setFormat("int32") - s - case t if t =:= typeOf[Int] => - val s = new IntegerSchema() - s.setFormat("int32") - s + private def handleSimpleType(tpe: Type): Schema[_] = { + val simpleType = getOpenAPISimpleType(tpe) + simpleType.format.foreach(simpleType.schema.setFormat) + simpleType._type.foreach(simpleType.schema.setType) + simpleType.schema + } + + private def getOpenAPISimpleType(tpe: Type): OpenAPISimpleType = tpe.dealias match { + case t if t =:= typeOf[Byte] || t =:= typeOf[Short] || t =:= typeOf[Int] => + OpenAPISimpleType(new IntegerSchema()) case t if t =:= typeOf[Long] => - val s = new IntegerSchema() - s.setFormat("int64") - s + OpenAPISimpleType(new IntegerSchema(), Some("int64")) case t if t =:= typeOf[Float] => - val s = new NumberSchema() - s.setFormat("float") - s + OpenAPISimpleType(new NumberSchema(), Some("float")) case t if t =:= typeOf[Double] => - val s = new NumberSchema() - s.setFormat("double") - s - case t if t =:= typeOf[Char] => - new StringSchema() - case t if t =:= typeOf[String] => - new StringSchema() + OpenAPISimpleType(new NumberSchema(), Some("double")) + case t if t =:= typeOf[Char] || t =:= typeOf[String] => + OpenAPISimpleType(new StringSchema()) case t if t =:= typeOf[UUID] => - val s = new UUIDSchema() - s.setFormat("uuid") - s + OpenAPISimpleType(new UUIDSchema()) case t if t =:= typeOf[Boolean] => - new BooleanSchema() + OpenAPISimpleType(new BooleanSchema()) case t if t =:= typeOf[Unit] => - val s = new Schema[Unit]() - s.setType("null") - s + OpenAPISimpleType(new Schema[Unit](), None, Some("null")) case t if t =:= typeOf[ZonedDateTime] || t =:= typeOf[Instant] || t =:= typeOf[LocalDateTime] || t =:= typeOf[Timestamp] => - val s = new StringSchema() - s.setFormat("date-time") - s + OpenAPISimpleType(new StringSchema(), Some("date-time")) case t if t =:= typeOf[LocalDate] => - val s = new StringSchema() - s.setFormat("date") - s + OpenAPISimpleType(new StringSchema(), Some("date")) case t if t =:= typeOf[LocalTime] => - val s = new StringSchema() - s.setFormat("time") - s + OpenAPISimpleType(new StringSchema(), Some("time")) case t if t =:= typeOf[BigDecimal] => - new NumberSchema() + OpenAPISimpleType(new NumberSchema()) case t if t =:= typeOf[BigInt] => - val s = new IntegerSchema() - s.setFormat(null) - s + OpenAPISimpleType(new IntegerSchema(), Some(null)) } private def registerAsReference(name: String, schema: Schema[_]): Schema[_] = { From 46a624467e009aaa860d68f2c1ac449a165ce32a Mon Sep 17 00:00:00 2001 From: Alex Guzman Date: Thu, 24 Jul 2025 14:59:51 +0200 Subject: [PATCH 12/12] review suggestion --- .../absa/springdocopenapiscala/OpenAPIModelRegistration.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala index 997b8dc..17aa0ce 100644 --- a/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala +++ b/library/src/main/scala/za/co/absa/springdocopenapiscala/OpenAPIModelRegistration.scala @@ -243,7 +243,7 @@ class OpenAPIModelRegistration( case t if t =:= typeOf[Boolean] => OpenAPISimpleType(new BooleanSchema()) case t if t =:= typeOf[Unit] => - OpenAPISimpleType(new Schema[Unit](), None, Some("null")) + OpenAPISimpleType(new Schema, None, Some("null")) case t if t =:= typeOf[ZonedDateTime] || t =:= typeOf[Instant] || t =:= typeOf[LocalDateTime] || t =:= typeOf[Timestamp] => OpenAPISimpleType(new StringSchema(), Some("date-time")) case t if t =:= typeOf[LocalDate] =>