Skip to content

Commit 902cd60

Browse files
authored
Merge pull request #574 from jarrodcodes/support-nonclass-types
#573 Handle nonclass Scala types and add tests
2 parents b7ce67f + 480eae1 commit 902cd60

File tree

2 files changed

+51
-29
lines changed

2 files changed

+51
-29
lines changed

Diff for: swagger/src/main/scala/org/http4s/rho/swagger/TypeBuilder.scala

+38-27
Original file line numberDiff line numberDiff line change
@@ -210,27 +210,34 @@ object TypeBuilder {
210210
ArrayProperty(items = itemProperty)
211211
} else if (tpe.isOption)
212212
typeToProperty(tpe.typeArgs.head, sfs).withRequired(false)
213-
else if (tpe.isAnyVal && !tpe.isPrimitive)
214-
typeToProperty(
215-
ptSym.asClass.primaryConstructor.asMethod.paramLists.flatten.head.typeSignature,
216-
sfs
217-
)
218-
else if (isCaseClass(ptSym) || (isSumType(ptSym) && !isObjectEnum(ptSym)))
219-
RefProperty(tpe.simpleName)
220-
else
221-
DataType.fromType(tpe) match {
222-
case DataType.ValueDataType(name, format, qName) =>
223-
AbstractProperty(`type` = name, description = qName, format = format)
224-
case DataType.ComplexDataType(name, qName) =>
225-
AbstractProperty(`type` = name, description = qName)
226-
case DataType.ContainerDataType(name, _, _) =>
227-
AbstractProperty(`type` = name)
228-
case DataType.EnumDataType(enums) =>
229-
StringProperty(enums = enums)
213+
else if (tpe.isAnyVal && !tpe.isPrimitive && ptSym.isClass) {
214+
val symbolOption = ptSym.asClass.primaryConstructor.asMethod.paramLists.flatten.headOption
215+
symbolOption match {
216+
case Some(symbol) =>
217+
typeToProperty(
218+
symbol.typeSignature,
219+
sfs
220+
)
221+
case None => dataTypeFromType(tpe)
230222
}
223+
} else if (isCaseClass(ptSym) || (isSumType(ptSym) && !isObjectEnum(ptSym)))
224+
RefProperty(tpe.simpleName)
225+
else dataTypeFromType(tpe)
231226
}
232227
)
233228

229+
private def dataTypeFromType(tpe: Type)(implicit showType: ShowType): Property =
230+
DataType.fromType(tpe) match {
231+
case DataType.ValueDataType(name, format, qName) =>
232+
AbstractProperty(`type` = name, description = qName, format = format)
233+
case DataType.ComplexDataType(name, qName) =>
234+
AbstractProperty(`type` = name, description = qName)
235+
case DataType.ContainerDataType(name, _, _) =>
236+
AbstractProperty(`type` = name)
237+
case DataType.EnumDataType(enums) =>
238+
StringProperty(enums = enums)
239+
}
240+
234241
sealed trait DataType {
235242
def name: String
236243
}
@@ -288,7 +295,6 @@ object TypeBuilder {
288295

289296
private[swagger] def fromType(t: Type)(implicit st: ShowType): DataType = {
290297
val klass = if (t.isOption && t.typeArgs.nonEmpty) t.typeArgs.head else t
291-
292298
if (klass.isNothingOrNull || klass.isUnitOrVoid)
293299
ComplexDataType("string", qualifiedName = Option(klass.fullName))
294300
else if (isString(klass)) this.String
@@ -312,21 +318,26 @@ object TypeBuilder {
312318
if (t.typeArgs.nonEmpty) GenArray(fromType(t.typeArgs(1)))
313319
else GenArray()
314320
} else if (klass <:< typeOf[AnyVal]) {
315-
fromType(
316-
klass.members
317-
.filter(_.isConstructor)
318-
.flatMap(_.asMethod.paramLists.flatten)
319-
.head
320-
.typeSignature
321-
)
321+
val klassSymbolOption = klass.members
322+
.filter(_.isConstructor)
323+
.flatMap(_.asMethod.paramLists.flatten)
324+
.headOption
325+
klassSymbolOption match {
326+
case Some(symbol) => fromType(symbol.typeSignature)
327+
case None => fallBackDataTypeFromName(t)
328+
}
322329
} else if (isObjectEnum(klass.typeSymbol)) {
323330
EnumDataType(klass.typeSymbol.asClass.knownDirectSubclasses.map(_.name.toString))
324331
} else {
325-
val stt = if (t.isOption) t.typeArgs.head else t
326-
ComplexDataType("string", qualifiedName = Option(stt.fullName))
332+
fallBackDataTypeFromName(t)
327333
}
328334
}
329335

336+
private def fallBackDataTypeFromName(t: Type)(implicit st: ShowType): DataType = {
337+
val stt = if (t.isOption) t.typeArgs.head else t
338+
ComplexDataType("string", qualifiedName = Option(stt.fullName))
339+
}
340+
330341
private[this] val IntTypes =
331342
Set[Type](
332343
typeOf[Int],

Diff for: swagger/src/test/scala/org/http4s/rho/swagger/TypeBuilderSuite.scala

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.http4s.rho.swagger
22

33
import java.sql.Timestamp
44
import java.util.Date
5-
65
import cats.effect.IO
76
import cats.syntax.all._
87
import fs2.Stream
@@ -29,13 +28,15 @@ package object model {
2928
case class FooWithMap(l: Map[String, Int])
3029
case class FooVal(foo: Foo) extends AnyVal
3130
case class BarWithFooVal(fooVal: FooVal)
31+
case class AnyValClass(anyVal: AnyVal)
32+
type AnyValType = AnyVal
33+
case class ClassWithAnyValType(anyVal: AnyValType)
3234
@DiscriminatorField("foobar")
3335
sealed trait Sealed {
3436
def foo: String
3537
}
3638
case class FooSealed(a: Int, foo: String, foo2: Foo) extends Sealed
3739
case class BarSealed(str: String, foo: String) extends Sealed
38-
3940
sealed trait SealedEnum
4041
case object FooEnum extends SealedEnum
4142
case object BarEnum extends SealedEnum
@@ -413,6 +414,16 @@ class TypeBuilderSuite extends FunSuite {
413414
assertEquals(model, modelOf[Sealed])
414415
}
415416

417+
test("A TypeBuilder should fall back to the class name for a class containing an AnyVal") {
418+
val m = modelOf[AnyValClass].head
419+
assertEquals(m.description, "AnyValClass".some)
420+
}
421+
422+
test("A TypeBuilder should fall back to the class name for a custom type containing an AnyVal") {
423+
val m = modelOf[ClassWithAnyValType].head
424+
assertEquals(m.description, "ClassWithAnyValType".some)
425+
}
426+
416427
test("A TypeBuilder should build a model for two-level sealed trait hierarchy") {
417428
val ms = modelOf[TopLevelSealedTrait]
418429
assertEquals(ms.size, 5)

0 commit comments

Comments
 (0)