Skip to content

Commit e6bef26

Browse files
committed
wip
1 parent 6b2c141 commit e6bef26

File tree

9 files changed

+239
-0
lines changed

9 files changed

+239
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
.bsp/sbt.json

build.sbt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Settings file for all the modules.
2+
import xml.Group
3+
import sbt._
4+
import Keys._
5+
6+
organization := "com.github.swagger-akka-http"
7+
8+
ThisBuild / scalaVersion := "3.0.2"
9+
10+
ThisBuild / organizationHomepage := Some(url("https://github.com/swagger-akka-http/swagger-scala3-enum-module"))
11+
12+
ThisBuild / scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked")
13+
14+
Test / publishArtifact := false
15+
16+
pomIncludeRepository := { x => false }
17+
18+
libraryDependencies ++= Seq(
19+
"io.swagger.core.v3" % "swagger-core-jakarta" % "2.1.11",
20+
"com.github.swagger-akka-http" %% "swagger-scala-module" % "2.5.2",
21+
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.13.0",
22+
"org.scalatest" %% "scalatest" % "3.2.10" % Test,
23+
"org.slf4j" % "slf4j-simple" % "1.7.32" % Test
24+
)
25+
26+
homepage := Some(new URL("https://github.com/swagger-akka-http/swagger-scala3-enum-module"))
27+
28+
Test / parallelExecution := false
29+
30+
startYear := Some(2021)
31+
32+
licenses := Seq(("Apache License 2.0", new URL("http://www.apache.org/licenses/LICENSE-2.0.html")))
33+
34+
releasePublishArtifactsAction := PgpKeys.publishSigned.value
35+
36+
pomExtra := {
37+
pomExtra.value ++ Group(
38+
<issueManagement>
39+
<system>github</system>
40+
<url>https://github.com/swagger-api/swagger-scala3-enum-module/issues</url>
41+
</issueManagement>
42+
<developers>
43+
<developer>
44+
<id>pjfanning</id>
45+
<name>PJ Fanning</name>
46+
<url>https://github.com/pjfanning</url>
47+
</developer>
48+
</developers>
49+
)
50+
}
51+
52+
ThisBuild / githubWorkflowJavaVersions := Seq("[email protected]")
53+
ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
54+
ThisBuild / githubWorkflowPublishTargetBranches := Seq(
55+
RefPredicate.Equals(Ref.Branch("main")),
56+
RefPredicate.StartsWith(Ref.Tag("v"))
57+
)
58+
59+
ThisBuild / githubWorkflowPublish := Seq(
60+
WorkflowStep.Sbt(
61+
List("ci-release"),
62+
env = Map(
63+
"PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}",
64+
"PGP_SECRET" -> "${{ secrets.PGP_SECRET }}",
65+
"SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}",
66+
"SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}"
67+
)
68+
)
69+
)
70+

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.5.5

project/plugins.sbt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0")
2+
3+
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
4+
5+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
6+
7+
addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.13.0")
8+
9+
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.github.swagger.scala3.enum.converter.SwaggerScala3EnumModelConverter
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.github.swagger.scala3.`enum`.converter
2+
3+
import com.github.swagger.scala.converter.AnnotatedTypeForOption
4+
import io.swagger.v3.core.converter.{AnnotatedType, ModelConverter, ModelConverterContext}
5+
import io.swagger.v3.core.jackson.ModelResolver
6+
import io.swagger.v3.core.util.{Json, PrimitiveType}
7+
import io.swagger.v3.oas.annotations.Parameter
8+
import io.swagger.v3.oas.annotations.media.Schema.AccessMode
9+
import io.swagger.v3.oas.annotations.media.Schema as SchemaAnnotation
10+
import io.swagger.v3.oas.models.media.Schema
11+
12+
import java.util.Iterator
13+
import scala.reflect.Enum
14+
15+
class SwaggerScala3EnumModelConverter extends ModelResolver(Json.mapper()) {
16+
private val enumEntryClass = classOf[Enum]
17+
18+
override def resolve(annotatedType: AnnotatedType, context: ModelConverterContext, chain: Iterator[ModelConverter]): Schema[_] = {
19+
val javaType = _mapper.constructType(annotatedType.getType)
20+
val cls = javaType.getRawClass
21+
if (isEnum(cls)) {
22+
val sp: Schema[String] = PrimitiveType.STRING.createProperty().asInstanceOf[Schema[String]]
23+
setRequired(annotatedType)
24+
getValues(cls).foreach { v =>
25+
sp.addEnumItemObject(v)
26+
}
27+
nullSafeList(annotatedType.getCtxAnnotations).foreach {
28+
case p: Parameter => {
29+
Option(p.description).foreach(desc => sp.setDescription(desc))
30+
sp.setDeprecated(p.deprecated)
31+
Option(p.example).foreach(ex => sp.setExample(ex))
32+
Option(p.name).foreach(name => sp.setName(name))
33+
}
34+
case s: SchemaAnnotation => {
35+
Option(s.description).foreach(desc => sp.setDescription(desc))
36+
Option(s.defaultValue).foreach(df => sp.setDefault(df))
37+
sp.setDeprecated(s.deprecated)
38+
Option(s.example).foreach(ex => sp.setExample(ex))
39+
Option(s.name).foreach(name => sp.setName(name))
40+
Option(s.accessMode).foreach { accessMode =>
41+
accessMode match {
42+
case AccessMode.READ_ONLY => sp.setReadOnly(true)
43+
case AccessMode.WRITE_ONLY => sp.setWriteOnly(true)
44+
case _ =>
45+
}
46+
}
47+
}
48+
case _ =>
49+
}
50+
sp
51+
} else if (chain.hasNext) {
52+
val nextResolved = Option(chain.next().resolve(annotatedType, context, chain))
53+
nextResolved match {
54+
case Some(property) => {
55+
setRequired(annotatedType)
56+
property
57+
}
58+
case None => None.orNull
59+
}
60+
} else {
61+
None.orNull
62+
}
63+
}
64+
65+
private def isEnum(cls: Class[_]): Boolean = enumEntryClass.isAssignableFrom(cls)
66+
67+
private def getValues(cls: Class[_]): Seq[String] = {
68+
val enumCompanion = Class.forName(cls.getName + "$").getField("MODULE$").get(null)
69+
val enumArray = enumCompanion.getClass.getDeclaredMethod("values").invoke(enumCompanion).asInstanceOf[Array[Enum]]
70+
enumArray.sortBy(_.ordinal).map(_.toString).toSeq
71+
}
72+
73+
private def setRequired(annotatedType: AnnotatedType): Unit = annotatedType match {
74+
case _: AnnotatedTypeForOption => // not required
75+
case _ => {
76+
val required = getRequiredSettings(annotatedType).headOption.getOrElse(true)
77+
if (required) {
78+
Option(annotatedType.getParent).foreach { parent =>
79+
Option(annotatedType.getPropertyName).foreach { n =>
80+
addRequiredItem(parent, n)
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
private def getRequiredSettings(annotatedType: AnnotatedType): Seq[Boolean] = annotatedType match {
88+
case _: AnnotatedTypeForOption => Seq.empty
89+
case _ => {
90+
nullSafeList(annotatedType.getCtxAnnotations).collect {
91+
case p: Parameter => p.required()
92+
case s: SchemaAnnotation => s.required()
93+
}
94+
}
95+
}
96+
97+
private def nullSafeList[T](array: Array[T]): List[T] = Option(array) match {
98+
case None => List.empty[T]
99+
case Some(arr) => arr.toList
100+
}
101+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.github.swagger.scala3.`enum`.converter
2+
3+
case class Car(make: String, color: ColorEnum)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.github.swagger.scala3.`enum`.converter
2+
3+
enum ColorEnum { case Red, Green, Blue }
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.github.swagger.scala3.`enum`.converter
2+
3+
import io.swagger.v3.core.converter.ModelConverters
4+
import io.swagger.v3.oas.models.media.{Schema, StringSchema}
5+
import org.scalatest.OptionValues
6+
import org.scalatest.matchers.should.Matchers
7+
import org.scalatest.wordspec.AnyWordSpec
8+
9+
import scala.jdk.CollectionConverters.*
10+
import scala.reflect.Enum
11+
12+
class SwaggerScala3EnumModelConverterSpec extends AnyWordSpec with Matchers with OptionValues {
13+
"SwaggerScala3EnumModelConverter" should {
14+
"deserialize Car" in {
15+
/*
16+
val carClass = classOf[Car]
17+
val enumClass = carClass.getMethods.toList.filter(_.getName == "color").map(_.getReturnType).head
18+
val enumCompanion = Class.forName(enumClass.getName + "$").getField("MODULE$").get(null)
19+
val enumArray = enumCompanion.getClass.getDeclaredMethod("values").invoke(enumCompanion).asInstanceOf[Array[Enum]]
20+
println(enumArray.map(_.toString).toList)
21+
*/
22+
val converter = ModelConverters.getInstance()
23+
val schemas = converter.readAll(classOf[Car]).asScala.toMap
24+
val model = findModel(schemas, "Car")
25+
model should be (defined)
26+
model.get.getProperties should not be (null)
27+
val field = model.value.getProperties.get("color")
28+
field shouldBe a [StringSchema]
29+
field.asInstanceOf[StringSchema].getEnum.asScala shouldEqual Seq("Red")
30+
nullSafeList(model.value.getRequired) shouldBe Seq("color")
31+
}
32+
}
33+
34+
private def findModel(schemas: Map[String, Schema[_]], name: String): Option[Schema[_]] = {
35+
schemas.get(name) match {
36+
case Some(m) => Some(m)
37+
case None =>
38+
schemas.keys.find { case k => k.startsWith(name) } match {
39+
case Some(key) => schemas.get(key)
40+
case None => schemas.values.headOption
41+
}
42+
}
43+
}
44+
45+
private def nullSafeList[T](list: java.util.List[T]): List[T] = Option(list) match {
46+
case None => List[T]()
47+
case Some(l) => l.asScala.toList
48+
}
49+
}

0 commit comments

Comments
 (0)