scalatra / scalatra

Tiny Scala high-performance, async web framework, inspired by Sinatra
http://scalatra.org
Other
2.66k stars 339 forks source link

swagger NullPointerException #343

Open pajooh opened 10 years ago

pajooh commented 10 years ago

based on official guide i added swagger to my project. in swagger annotation, i have something like

val getBookss =
  (apiOperation[List[Book]]("getBooks")
...

which Book is database connection object from scala-activerecord:

case class Book(var name: String, var age: Int) extends ActiveRecord
object Book extends ActiveRecordCompanion[Book]

i get java.lang.NullPointerException error arroung my apiOperation definitin. also, upgrading to latest scalatra 2.3.0-M1 with latest swagger can not help, here is my error log:

java.lang.NullPointerException: null
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$findScalaSig$1.apply(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$findScalaSig$1.apply(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at scala.Option.orElse(Option.scala:257) ~[scala-library-2.10.3.jar:na]
        at org.scalatra.swagger.reflect.ScalaSigReader$.findScalaSig(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$findScalaSig$1.apply(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$findScalaSig$1.apply(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at scala.Option.orElse(Option.scala:257) ~[scala-library-2.10.3.jar:na]
        at org.scalatra.swagger.reflect.ScalaSigReader$.findScalaSig(ScalaSigReader.scala:133) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$.findClass(ScalaSigReader.scala:41) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$.org$scalatra$swagger$reflect$ScalaSigReader$$read$1(ScalaSigReader.scala:35) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$org$scalatra$swagger$reflect$ScalaSigReader$$read$1$1.apply(ScalaSigReader.scala:35) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$$anonfun$org$scalatra$swagger$reflect$ScalaSigReader$$read$1$1.apply(ScalaSigReader.scala:35) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at scala.Option.getOrElse(Option.scala:120) ~[scala-library-2.10.3.jar:na]
        at org.scalatra.swagger.reflect.ScalaSigReader$.org$scalatra$swagger$reflect$ScalaSigReader$$read$1(ScalaSigReader.scala:35) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.ScalaSigReader$.readField(ScalaSigReader.scala:37) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$$anonfun$2.apply(Reflector.scala:92) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$$anonfun$2.apply(Reflector.scala:91) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) ~[scala-library-2.10.3.jar:na]
        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) ~[scala-library-2.10.3.jar:na]
        at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59) ~[scala-library-2.10.3.jar:na]
        at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:47) ~[scala-library-2.10.3.jar:na]
        at scala.collection.TraversableLike$class.map(TraversableLike.scala:244) ~[scala-library-2.10.3.jar:na]
        at scala.collection.AbstractTraversable.map(Traversable.scala:105) ~[scala-library-2.10.3.jar:na]
        at org.scalatra.swagger.reflect.Reflector$.fields$1(Reflector.scala:91) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$.fields$1(Reflector.scala:102) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$.properties$1(Reflector.scala:105) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$.createDescriptor(Reflector.scala:159) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$$anonfun$describe$2.apply(Reflector.scala:44) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$$anonfun$describe$2.apply(Reflector.scala:44) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.package$Memo.apply(package.scala:16) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.reflect.Reflector$.describe(Reflector.scala:44) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.Swagger$.collectModels(Swagger.scala:59) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.Swagger$.collectModels(Swagger.scala:48) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.swagger.SwaggerSupportSyntax$class.registerModel(SwaggerSupport.scala:398) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at rwa.controllers.IndicatorController.registerModel(IndicatorController.scala:13) ~[classes/:na]
        at org.scalatra.swagger.SwaggerSupport$class.apiOperation(SwaggerSupport.scala:511) ~[scalatra-swagger_2.10-2.3.0.M1.jar:2.3.0.M1]
        at rwa.controllers.IndicatorController.apiOperation(IndicatorController.scala:13) ~[classes/:na]
        at rwa.controllers.IndicatorController.<init>(IndicatorController.scala:26) ~[classes/:na]
        at ScalatraBootstrap.init(ScalatraBootstrap.scala:13) ~[classes/:na]
        at org.scalatra.servlet.ScalatraListener.configureCycleClass(ScalatraListener.scala:67) ~[scalatra_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.scalatra.servlet.ScalatraListener.contextInitialized(ScalatraListener.scala:23) ~[scalatra_2.10-2.3.0.M1.jar:2.3.0.M1]
        at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:799) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:434) [jetty-servlet-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:791) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:284) [jetty-servlet-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1346) [jetty-webapp-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:743) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:491) [jetty-webapp-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:117) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:99) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:154) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:117) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.Server.start(Server.java:355) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:99) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.server.Server.doStart(Server.java:324) [jetty-server-9.1.0.v20131115.jar:9.1.0.v20131115]
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69) [jetty-util-9.1.0.v20131115.jar:9.1.0.v20131115]
        at com.earldouglas.xsbtwebplugin.Jetty9Runner.start(Jetty9Runner.scala:103) [xsbt-web-plugin-0.5.0.jar:0.5.0]
        at com.earldouglas.xsbtwebplugin.Container$$anonfun$containerSettings$7.apply(Container.scala:64) [xsbt-web-plugin-0.5.0.jar:0.5.0]
        at com.earldouglas.xsbtwebplugin.Container$$anonfun$containerSettings$7.apply(Container.scala:63) [xsbt-web-plugin-0.5.0.jar:0.5.0]
        at scala.Function7$$anonfun$tupled$1.apply(Function7.scala:35) [scala-library.jar:0.13.0]
        at scala.Function7$$anonfun$tupled$1.apply(Function7.scala:34) [scala-library.jar:0.13.0]
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47) [scala-library.jar:0.13.0]
        at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:42) [collections-0.13.0.jar:0.13.0]
        at sbt.std.Transform$$anon$4.work(System.scala:64) [task-system-0.13.0.jar:0.13.0]
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237) [tasks-0.13.0.jar:0.13.0]
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237) [tasks-0.13.0.jar:0.13.0]
        at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18) [control-0.13.0.jar:0.13.0]
        at sbt.Execute.work(Execute.scala:244) [tasks-0.13.0.jar:0.13.0]
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237) [tasks-0.13.0.jar:0.13.0]
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237) [tasks-0.13.0.jar:0.13.0]
        at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:160) [tasks-0.13.0.jar:0.13.0]
        at sbt.CompletionService$$anon$2.call(CompletionService.scala:30) [tasks-0.13.0.jar:0.13.0]
        at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_45]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_45]
        at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_45]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_45]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_45]
        at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]
Verneri commented 10 years ago

I'm having similar problem with a field that is a scala enumeration

for example:

object something extends Enumeration {
   type EnumeratedType = Value
   val aValue, anotherValue = Value
}

case class DataWithEnum(field: something.EnumeratedType)

apiOperation[DataWithEnum]("getData")
casualjim commented 10 years ago

Enumerations are not supported

Verneri commented 10 years ago

Ok, but as json4s supports them rather nicely it would be nice to have some way to circumvent this problem by for example having swagger see them as strings or ints (depending on serialization).

I mean that is there anyway to register just that one field of a model to be string and the rest come normally from the class. wihtou having to build the whole class model manually

phillro commented 10 years ago

JSON4S supports enumerations just fine. Whats the problem here?

casualjim commented 10 years ago

Json4s only supports enumerations out of dumb luck more or less. If you have 2 Enum Values that are the same string it can't differentiate.

The problem is that the type Value of an enumeration is defined on the Enumeration type so we really have no idea which enum we're dealing with at runtime.

fgaule commented 10 years ago

Is there a plan how to fix it or just will never be supported?

crypticmind commented 10 years ago

Given the understandable limitation of Enumeration objects, is there any way we can pass extra info to Swagger to overcome this situation and get the attribute exposed as a string at the very least?

casualjim commented 10 years ago

yes there is. You can use a model definition direclty and register it.

For example for this scalabuff generated enumeration (which in itself is already nicer than the default scala one)

object ComponentType extends net.sandrogrzicic.scalabuff.Enum {
  sealed trait EnumVal extends Value
  val _UNINITIALIZED = new EnumVal { val name = "UNINITIALIZED ENUM VALUE"; val id = -1 }

  val SERVICE = new EnumVal { val name = "SERVICE"; val id = 0 }
  val TASK = new EnumVal { val name = "TASK"; val id = 1 }
  val CRON = new EnumVal { val name = "CRON"; val id = 2 }
  val SPARK_JOB = new EnumVal { val name = "SPARK_JOB"; val id = 3 }

  val SERVICE_VALUE = 0
  val TASK_VALUE = 1
  val CRON_VALUE = 2
  val SPARK_JOB_VALUE = 3

  def valueOf(id: Int) = id match {
    case 0 => SERVICE
    case 1 => TASK
    case 2 => CRON
    case 3 => SPARK_JOB
    case _default => throw new net.sandrogrzicic.scalabuff.UnknownEnumException(_default)
  }
  val internalGetValueMap = new com.google.protobuf.Internal.EnumLiteMap[EnumVal] {
    def findValueByNumber(id: Int): EnumVal = valueOf(id)
  }
}

I have this json serializer:

class ComponentTypeSerializer extends CustomSerializer[CT.EnumVal]({ implicit fmts =>({
  case JString(s) if s.toUpperCase(Locale.ENGLISH) == CT.SERVICE.name => CT.SERVICE
  case JString(s) if s.toUpperCase(Locale.ENGLISH) == CT.TASK.name => CT.TASK
  case JString(s) if s.toUpperCase(Locale.ENGLISH) == CT.CRON.name => CT.CRON
  case JString(s) if s.toUpperCase(Locale.ENGLISH) == CT.SPARK_JOB.name => CT.SPARK_JOB
},{
  case b: AD.EnumVal => JString(b.name)
})})

And I use it in a model like this:

import protocol.{ComponentType => CT}
val applicationComponent = Model(
  id = "App",
  name = "App",
  qualifiedName = Some("protocol.App"),
  description = Some("A single executable component of a service"),
  properties = List(
    "name" -> ModelProperty(
      `type` = DataType.String,
      position = 0,
      required = true,
      description = Some("The name of this individual component")),
    "componentType" -> ModelProperty(
      `type` = DataType.String,
      position = 13,
      required = true,
      description = Some("The type of this component"),
      allowableValues = AllowableValues(CT.SERVICE.name, CT.TASK.name, CT.SPARK_JOB.name, CT.CRON.name))
  )
)

And in the scalatra servlet I've registered the model:

class MyServlet extends ScalatraServlet with SwaggerSupport {
  registerModel(applicationComponent)

}
casualjim commented 10 years ago

it's not that this won't be solved but it won't be solved in the 2.3 series. In 2.4 we're going to start using macros for more and more things to work around many of the rough edges we still have. This being one of them.

crypticmind commented 10 years ago

I have created a small project using macros to generate the Swagger model. It's still missing some types like Map for instance, but it can handle primitives, complex types, Scala enumerations, and Iterables. I wish this could help you guys in 2.4 to approach this problem.

You can find the code at https://github.com/crypticmind/scala-swagger-modelgen

casualjim commented 10 years ago

Swagger is undergoing a big overhaul, so the spec will change. But YES thank you very much. I'll pull this in, when I get round to upgrading the swagger support in scalatra for 2.4

nikolay-bukharev commented 9 years ago

any idea when it would be fixed?

tekumara commented 8 years ago

Still an issue in 2.4.1. Will work-around by building a custom model, which will need to be manually kept in sync when I change the case classes it maps. But of a pain though because it's a deeply nested model.