2
votes

I am trying to create this api for objects like user, location, and visits. And I want to have post methods for adding and updating values of these objects. However, I do not know how to pass the object to the route of an api. A part of my route for the location update:

trait ApiRoute extends MyDatabases with FailFastCirceSupport {
     val routes =
          pathPrefix("locations") {
            pathSingleSlash {
              pathPrefix(LongNumber) { id =>
                  post { entity(as[Location]){
                    location => {
                      onSuccess(locationRepository.update(location)) {
                        result => complete(result.asJson)
                      }
                    }
                  }
}

However, when I try to build update in such a way I get an error:

could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[models.Location]
[error]               post { entity(as[Location]){

Json encoder for the location:

package object application {
      implicit val locationEncoder = new Encoder[Location] {
        final def apply(location: Location): Json = Json.obj(
          ("id", Json.fromLong(location.id)),
          ("place", Json.fromString(location.place)),
          ("country", Json.fromString(location.country)),
          ("city", Json.fromString(location.city)),
          ("distance", Json.fromLong(location.distance))
        )
      }

I am using Slick to model and get all the data from the database:

case class Location (id: Long, place: String, country: String, city: String, distance: Long)

class LocationTable(tag: Tag) extends Table[Location](tag, "locations") {
  val id = column[Long]("id", O.PrimaryKey)
  val place = column[String]("place")
  val country = column[String]("country")
  val city = column[String]("city")
  val distance = column[Long]("distance")

  def * = (id, place, country, city, distance) <> (Location.apply _ tupled, Location.unapply)
}

object LocationTable {
  val table = TableQuery[LocationTable]
}

class LocationRepository(db: Database) {
  val locationTableQuery = TableQuery[LocationTable]

  def create(location: Location): Future[Location] =
    db.run(locationTableQuery returning locationTableQuery += location)

  def update(location: Location): Future[Int] =
    db.run(locationTableQuery.filter(_.id === location.id).update(location))
}

So what should I add or change in my code to get rid of the exception and make it work?

1

1 Answers

3
votes

If you are adding or updating a Location, that Location needs a Decoder as well to read the serialized data from the client that comes across the wire as the HTTP entity. Akka HTTP also needs a FromRequestUnmarshaller in conjunction with the Decoder to decode the request entity which in this example is the Location you want to add or update, and the one that you extract the id from.

Using Scala's Circe library for JSON handling, then the Akka HTTP JSON project has part of what you need. As that project indicates, add the following to your build.sbt

// All releases including intermediate ones are published here,
// final ones are also published to Maven Central.
resolvers += Resolver.bintrayRepo("hseeberger", "maven")

libraryDependencies ++= List(
  "de.heikoseeberger" %% "akka-http-circe" % "1.18.0"
)

and then you can mix in the support you need using FailFastCirceSupport or ErrorAccumulatingCirceSupport. Add that to the class that defines your routes with

class SomeClassWithRoutes extends ErrorAccumulatingCirceSupport

or

class SomeClassWithRoutes extends FailFastCirceSupport

depending on whether you want to fail on the first error, if any, or accumulate them.

You still need to have a Decoder[Location] in scope as well. For that you can see Circe's documentation, but one quick way to define that if you want the default field names is to use the following imports in your route definition class or file so that Circe creates the necessary Decoder for you.

import io.circe.generic.auto._
import io.circe.Decoder