Skip to content

Commit

Permalink
Merge pull request #574 from jarrodcodes/support-nonclass-types
Browse files Browse the repository at this point in the history
#573 Handle nonclass Scala types and add tests
  • Loading branch information
zarthross authored Aug 3, 2021
2 parents b7ce67f + 480eae1 commit 902cd60
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 29 deletions.
65 changes: 38 additions & 27 deletions swagger/src/main/scala/org/http4s/rho/swagger/TypeBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,27 +210,34 @@ object TypeBuilder {
ArrayProperty(items = itemProperty)
} else if (tpe.isOption)
typeToProperty(tpe.typeArgs.head, sfs).withRequired(false)
else if (tpe.isAnyVal && !tpe.isPrimitive)
typeToProperty(
ptSym.asClass.primaryConstructor.asMethod.paramLists.flatten.head.typeSignature,
sfs
)
else if (isCaseClass(ptSym) || (isSumType(ptSym) && !isObjectEnum(ptSym)))
RefProperty(tpe.simpleName)
else
DataType.fromType(tpe) match {
case DataType.ValueDataType(name, format, qName) =>
AbstractProperty(`type` = name, description = qName, format = format)
case DataType.ComplexDataType(name, qName) =>
AbstractProperty(`type` = name, description = qName)
case DataType.ContainerDataType(name, _, _) =>
AbstractProperty(`type` = name)
case DataType.EnumDataType(enums) =>
StringProperty(enums = enums)
else if (tpe.isAnyVal && !tpe.isPrimitive && ptSym.isClass) {
val symbolOption = ptSym.asClass.primaryConstructor.asMethod.paramLists.flatten.headOption
symbolOption match {
case Some(symbol) =>
typeToProperty(
symbol.typeSignature,
sfs
)
case None => dataTypeFromType(tpe)
}
} else if (isCaseClass(ptSym) || (isSumType(ptSym) && !isObjectEnum(ptSym)))
RefProperty(tpe.simpleName)
else dataTypeFromType(tpe)
}
)

private def dataTypeFromType(tpe: Type)(implicit showType: ShowType): Property =
DataType.fromType(tpe) match {
case DataType.ValueDataType(name, format, qName) =>
AbstractProperty(`type` = name, description = qName, format = format)
case DataType.ComplexDataType(name, qName) =>
AbstractProperty(`type` = name, description = qName)
case DataType.ContainerDataType(name, _, _) =>
AbstractProperty(`type` = name)
case DataType.EnumDataType(enums) =>
StringProperty(enums = enums)
}

sealed trait DataType {
def name: String
}
Expand Down Expand Up @@ -288,7 +295,6 @@ object TypeBuilder {

private[swagger] def fromType(t: Type)(implicit st: ShowType): DataType = {
val klass = if (t.isOption && t.typeArgs.nonEmpty) t.typeArgs.head else t

if (klass.isNothingOrNull || klass.isUnitOrVoid)
ComplexDataType("string", qualifiedName = Option(klass.fullName))
else if (isString(klass)) this.String
Expand All @@ -312,21 +318,26 @@ object TypeBuilder {
if (t.typeArgs.nonEmpty) GenArray(fromType(t.typeArgs(1)))
else GenArray()
} else if (klass <:< typeOf[AnyVal]) {
fromType(
klass.members
.filter(_.isConstructor)
.flatMap(_.asMethod.paramLists.flatten)
.head
.typeSignature
)
val klassSymbolOption = klass.members
.filter(_.isConstructor)
.flatMap(_.asMethod.paramLists.flatten)
.headOption
klassSymbolOption match {
case Some(symbol) => fromType(symbol.typeSignature)
case None => fallBackDataTypeFromName(t)
}
} else if (isObjectEnum(klass.typeSymbol)) {
EnumDataType(klass.typeSymbol.asClass.knownDirectSubclasses.map(_.name.toString))
} else {
val stt = if (t.isOption) t.typeArgs.head else t
ComplexDataType("string", qualifiedName = Option(stt.fullName))
fallBackDataTypeFromName(t)
}
}

private def fallBackDataTypeFromName(t: Type)(implicit st: ShowType): DataType = {
val stt = if (t.isOption) t.typeArgs.head else t
ComplexDataType("string", qualifiedName = Option(stt.fullName))
}

private[this] val IntTypes =
Set[Type](
typeOf[Int],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.http4s.rho.swagger

import java.sql.Timestamp
import java.util.Date

import cats.effect.IO
import cats.syntax.all._
import fs2.Stream
Expand All @@ -29,13 +28,15 @@ package object model {
case class FooWithMap(l: Map[String, Int])
case class FooVal(foo: Foo) extends AnyVal
case class BarWithFooVal(fooVal: FooVal)
case class AnyValClass(anyVal: AnyVal)
type AnyValType = AnyVal
case class ClassWithAnyValType(anyVal: AnyValType)
@DiscriminatorField("foobar")
sealed trait Sealed {
def foo: String
}
case class FooSealed(a: Int, foo: String, foo2: Foo) extends Sealed
case class BarSealed(str: String, foo: String) extends Sealed

sealed trait SealedEnum
case object FooEnum extends SealedEnum
case object BarEnum extends SealedEnum
Expand Down Expand Up @@ -413,6 +414,16 @@ class TypeBuilderSuite extends FunSuite {
assertEquals(model, modelOf[Sealed])
}

test("A TypeBuilder should fall back to the class name for a class containing an AnyVal") {
val m = modelOf[AnyValClass].head
assertEquals(m.description, "AnyValClass".some)
}

test("A TypeBuilder should fall back to the class name for a custom type containing an AnyVal") {
val m = modelOf[ClassWithAnyValType].head
assertEquals(m.description, "ClassWithAnyValType".some)
}

test("A TypeBuilder should build a model for two-level sealed trait hierarchy") {
val ms = modelOf[TopLevelSealedTrait]
assertEquals(ms.size, 5)
Expand Down

0 comments on commit 902cd60

Please sign in to comment.