Search code examples
akka-httpspray-json

properly unmarshalling trait in akka http


I've been through the docs and many questions on SO, still can't figure out what I'm doing wrong.

I'm starting with a vanilla akka http g8 template, but I want to use a trait rather than a case class to represent the objects which are coming in POST requests.

When using case classes everything is fine in my app, but when I try to implement traits it fails with:

 [error] CollectionRoutes.scala:38: could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[collector.CollectorConfig]
 [error]               entity(as[CollectorConfig]) { config =>
 [error]                        ^
 [error] CollectionRoutes.scala:55: could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[collector.CollectorStep]
 [error]                 entity(as[CollectorStep]) { collstep =>
 [error]                          ^
 [error] CollectionRoutes.scala:77: could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[collector.CollectorConfig]
 [error]                 entity(as[CollectorConfig]) { config =>
 [error]                          ^
 [error] three errors found

This is the trait I use to define the json formats:

trait JsonSupport extends DefaultJsonProtocol {
      import spray.json._

      implicit val collectionTypeConverter = new EnumJsonConverter(CollectionType)

      implicit val actionPerformedJsonFormat = jsonFormat1(ActionPerformed)
      implicit val restCollectorStepFormat = jsonFormat3(RestCollectorStep)
      implicit object collectorStepFormat extends RootJsonFormat[CollectorStep] {
        def write(a: CollectorStep) = a match {
          case p: RestCollectorStep => p.toJson
        }
        def read(value: JsValue) =
          value.asJsObject.fields("collectionType") match {
            case JsString("Rest") => value.convertTo[RestCollectorStep]
          }
      }

      implicit val restCollectorConfigFormat = jsonFormat5(RestCollectorConfig)
      implicit object collectorConfigFormat extends RootJsonFormat[CollectorConfig] {
        def write(a: CollectorConfig) = a match {
          case p: RestCollectorConfig => p.toJson
        }
        def read(value: JsValue) =
          value.asJsObject.fields("collectionType") match {
            case JsString("Rest") => value.convertTo[RestCollectorConfig]
          }
      }

    }

This is the trait which defines my routes:

trait CollectionRoutes extends JsonSupport {

        import spray.json._
        implicit def system: ActorSystem
        lazy val log = Logging(system, classOf[CollectionRoutes])

        def collectorRegistryActor: ActorRef

        implicit lazy val timeout = Timeout(5.seconds) // usually we'd obtain the timeout from the system's configuration

        val settings = CorsSettings.defaultSettings.withAllowGenericHttpRequests(true)
        lazy val collectionRoutes: Route = cors(settings) {
          pathPrefix("createcollector") {
            concat(
              pathEnd {
                concat(
                  post {
                    entity(as[CollectorConfig]) { config =>
                      val collectorCreated: Future[CollectorRegistryActor.ActionPerformed] =
                        (collectorRegistryActor ? MakeCollector(config)).mapTo[CollectorRegistryActor.ActionPerformed]
                      onSuccess(collectorCreated) { created =>
                        complete(created.description)
                      }
                    }
                  }
                )
              }
            )
          } ~
            pathPrefix("reststep") {
              concat(
                pathEnd {
                  concat(
                    post {
                      entity(as[CollectorStep]) { collstep =>
                        val executionResult: Future[StepActor.StepResponse] = (collectorRegistryActor ? ExecuteStep(collstep)).mapTo[StepActor.StepResponse]
                        onSuccess(executionResult) { res =>
                          res match {
                            case StepActor.StepResult(step, response, result) => {
                              val res = (response :: result :: Nil).toJson.toString
                              complete(res)
                            }
                            case StepActor.StepError(step, error) => complete(error)
                          }
                        }
                      }
                    }
                  )
                }
              )
            } ~
            pathPrefix("restcollect") {
              concat(
                pathEnd {
                  concat(
                    post {
                      entity(as[CollectorConfig]) { config =>
                        val executionResult: Future[CollectorActor.CollectionResult] = (collectorRegistryActor ? ExecuteCollection(config)).mapTo[CollectorActor.CollectionResult]
                        onSuccess(executionResult) { response =>
                          complete(response.result)
                        }
                      }
                    }
                  )
                }
              )
            }
        }

      }

And this is how that trait is being used.

 object CollectionServer extends App with CollectionRoutes {

        implicit val system: ActorSystem = ActorSystem("CollectionServer")
        implicit val materializer: ActorMaterializer = ActorMaterializer()

        val collectorRegistryActor: ActorRef = system.actorOf(CollectorRegistryActor.props, "collectorRegistryActor")

        lazy val routes: Route = collectionRoutes

        Http().bindAndHandle(routes, "localhost", 9100)

        println(s"Server online at http://localhost:9100/")

        Await.result(system.whenTerminated, Duration.Inf)
      }

I'm baffled because I believe I've set everything up in a very standard way, but it's not picking up the implicits.

Any ideas appreciated - thanks.


Solution

  • Figured this out:

    I should have been extending SprayJsonSupport rather than DefaultJsonProtocol

    trait JsonSupport extends SprayJsonSupport {