Play Framework: How to define a writable object in scala? - scala

Using Play, I have an object called RepositoryMetadata. I want to use that object in a method called post. The definition of that method is given below.
def post[T](body: T)(implicit wrt: Writeable[T], ct: ContentTypeOf[T]): Future[WSResponse].
How do I make the object RepositoryMetadata into Writeable.

For anyone interested in, I've run into a similar issue when using Play's WSClient. The version which I'm currently using (2.5.3) has the following signature:
def post[T](body: T)(implicit wrt: Writeable[T]): Future[WSResponse]
If you happen to need to post the payload as a json (as long as you already have a play.api.libs.json.Writes converter defined for your class) you could have something like below:
import play.api.http.{ContentTypeOf, ContentTypes, Writeable}
import play.api.libs.json.Writes
import play.api.mvc.Codec
trait WritableImplicits {
implicit def jsonWritable[A](implicit writes: Writes[A], codec: Codec): Writeable[A] = {
implicit val contentType = ContentTypeOf[A](Some(ContentTypes.JSON))
val transform = Writeable.writeableOf_JsValue.transform compose (writes.writes _)
Writeable(transform)
}
}
object WritableImplicits extends WritableImplicits
and then
import WritableImplicits._
...
val metadata: RepositoryMetadata = ???
wsClient.url(url).post(metadata)
...
And that should be it!
NOTE:
If you don't have an implicit Writes defined in scope, you could just do the following:
import play.api.libs.json._
object RepositoryMetadata {
implicit val repositoryMetadataWrites = Json.writes[RepositoryMetadata]
}

You will need to include two implicits:
import play.api.http._
import play.api.mvc._
implicit def writeable(implicit codec: Codec): Writeable[RepositoryMetadata] = {
// assuming RepositoryMetadata has a .toString
Writeable(data => codec.encode(data.toString))
}
implicit def contentType(implicit codec: Codec): ContentTypeOf[RepoositoryMetadata] = {
// for text/plain
ContentTypeOf(Some(ContentTypes.TEXT))
}
the two imports import the following:
play.api.http.ContentTypes
play.api.http.ContentTypeOf
play.api.http.Writeable
play.api.mvc.Codec

Related

How to rebuild json from api url using ZIO 2+ and scala 3+

I need to get json from https://api.opendota.com/api/leagues and rebuild it keeping only leagues name and leagues id, save to local file and send this json as a response to client by Get method
package dev.zio.quickstart.dota
import zhttp.http.\*
import zio.\*
import zio.json.\*
import zio.\_
import zio.http.Client
An http app that:
Accepts a `Request` and returns a `Response`
May fail with type of `Throwable`
Uses a `DotaRepo` as the environment
object DotaApp:
def apply(): Http\[DotaRepo, Throwable, Request, Response\] =
Http.collectZIO\[Request\] {
// GET /Leagues
case Method.GET -\> !! / "Leagues" =\>
val url = "https://api.opendota.com/api/leagues"
val program = for {
res \<- Client.request(url)
data \<- res.body.asString
} yield data.map(response =\> Response.json(response.toJson))
}
package dev.zio.quickstart.dota
import java.util.UUID
import zio.json.\*
case class League(name: String, leagueid: Int)
object League:
given JsonEncoder\[League\] =
DeriveJsonEncoder.gen\[League\]
given JsonDecoder\[League\] =
DeriveJsonDecoder.gen\[League\]\`
package dev.zio.quickstart.dota
import zio.\*
trait DotaRepo:
def leagues: Task\[List\[League\]\]
def liveMatches: Task\[List\[LiveMatch\]\]
object DotaRepo:
def leagues: ZIO\[DotaRepo, Throwable, List\[League\]\] =
ZIO.serviceWithZIO\[DotaRepo\](\_.leagues)
def liveMatches: ZIO[DotaRepo, Throwable, List[LiveMatch]] =
ZIO.serviceWithZIO[DotaRepo](_.liveMatches)
package dev.zio.quickstart.dota
import zio.\*
import scala.collection.mutable
case class InmemoryLeague(listBuffer: Ref\[mutable.ListBuffer\[League\]\]) extends DotaRepo:
def getLeagues: UIO\[List\[League\]\] =
listBuffer.get.map(\_.toList)
object InmemoryLeague {
def layer: ZLayer\[Any, Nothing, InmemoryLeague\] =
ZLayer.fromZIO(
Ref.make(mutable.ListBuffer.empty\[League\]).map(new InmemoryLeague(\_))
)
}
I've tried using these examples https://github.com/zio/zio-http/tree/main/zio-http-example/src/main/scala/example
Can't get in to syntax

Could not find implicit value for parameter write eror, yet I defined the handler using the macro

I have the following:
Account.scala
package modules.accounts
import java.time.Instant
import reactivemongo.api.bson._
case class Account(id: String, name: String)
object Account {
type ID = String
implicit val accountHandler: BSONDocumentHandler[Account] = Macros.handler[Account]
// implicit def accountWriter: BSONDocumentWriter[Account] = Macros.writer[Account]
// implicit def accountReader: BSONDocumentReader[Account] = Macros.reader[Account]
}
AccountRepo.scala
package modules.accounts
import java.time.Instant
import reactivemongo.api.collections.bson.BSONCollection
import scala.concurrent.ExecutionContext
final class AccountRepo(
val coll: BSONCollection
)(implicit ec: ExecutionContext) {
import Account.{ accountHandler, ID }
def insertTest() = {
val doc = Account(s"account123", "accountName") //, Instant.now)
coll.insert.one(doc)
}
}
The error I am getting is:
could not find implicit value for parameter writer: AccountRepo.this.coll.pack.Writer[modules.accounts.Account]
[error] coll.insert.one(doc)
From what I understand the implicit handler that is generated by the macro should be enough and create the Writer. What am I doing wrong?
Reference: http://reactivemongo.org/releases/1.0/documentation/bson/typeclasses.html
The code is mismixing different versions.
The macro generated handler is using the new BSON API, as it can be seen with the import reactivemongo.api.bson, whereas the collection is using an old driver, as it can be seen as it uses reactivemongo.api.collections.bson instead of reactivemongo.api.bson.collection.
It's recommended to have a look at the documentation, and not mixing incompatible versions of related libraries.

ReactiveMongo with generics: "ambiguous implicit values"

I'm not really sure where this error is coming from. I'm trying to migrate from an old .find() method where I could pass Pattern: query: BSONDocument to find and call .one[T] on it. This worked perfectly fine, but I'd like to follow the recommendations.
However, I have a feeling that my desire to create a generic database access object is causing some of this headache... take a look below.
The object that i'm trying to read from:
package Models
import reactivemongo.api.bson.{BSONDocumentHandler, Macros}
case class Person(name: String)
object Person {
implicit val handler: BSONDocumentHandler[Person] = Macros.handler[Person]
}
The class containing the method I'm trying to execute (ignore the Await, etc.)
package Data
import reactivemongo.api.AsyncDriver
import reactivemongo.api.bson._
import reactivemongo.api.bson.collection.BSONCollection
import reactivemongo.api.commands.WriteResult
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.reflect.ClassTag
object DataService extends DataService
trait DataService {
implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
private val driver = new AsyncDriver
private val connection = Await.result(driver.connect(List("localhost:32768")), 10.seconds)
private val database = Await.result(connection.database("database"), 10.seconds)
private def getCollection[T]()(implicit tag: ClassTag[T]): BSONCollection = database.collection(tag.runtimeClass.getSimpleName)
def Get[T]()(implicit handler: BSONDocumentHandler[T], tag: ClassTag[T]): Future[Option[T]] = {
val collection = getCollection[T]
collection.find(BSONDocument("name" -> "hello"), Option.empty).one[T]
}
}
How I'm trying to get it (ignore the Await, this is just for test purposes):
val personName = Await.result(dataService.Get[Person](), 10.seconds).map(_.name).getOrElse("Not found")
The error that I'm getting:
Error:(32, 19) ambiguous implicit values:
both object BSONDocumentIdentity in trait BSONIdentityHandlers of type reactivemongo.api.bson.package.BSONDocumentIdentity.type
and value handler of type reactivemongo.api.bson.BSONDocumentHandler[T]
match expected type collection.pack.Writer[J]
collection.find(BSONDocument("name" -> "hello"), Option.empty).cursor[T]
Thank you all for your help.
Try to add type to your option in your second argument find method, for example:
def Get[T]()(implicit handler: BSONDocumentHandler[T], tag: ClassTag[T]): Future[Option[T]] = {
val collection = getCollection[T]
collection.find(BSONDocument("name" -> "hello"), Option.empty[BSONDocument]).one[T]
}

Implicits getting out of scope in Spray example code: what's happening here?

I have copied Spray Client's example code into my own project, to have it easily available. I use IntelliJ 13.
Here is the code I have:
package mypackage
import scala.util.Success
import scala.concurrent.duration._
import akka.actor.ActorSystem
import akka.pattern.ask
import akka.event.Logging
import akka.io.IO
import spray.json.{JsonFormat, DefaultJsonProtocol}
import spray.can.Http
import spray.util._
import spray.client.pipelining._
import scala.util.Success
import scala.util.Failure
case class Elevation(location: Location, elevation: Double)
case class Location(lat: Double, lng: Double)
case class GoogleApiResult[T](status: String, results: List[T])
object ElevationJsonProtocol extends DefaultJsonProtocol {
implicit val locationFormat = jsonFormat2(Location)
implicit val elevationFormat = jsonFormat2(Elevation)
implicit def googleApiResultFormat[T :JsonFormat] = jsonFormat2(GoogleApiResult.apply[T])
}
object SprayExample extends App {
// we need an ActorSystem to host our application in
implicit val system = ActorSystem("simple-spray-client")
import system.dispatcher // execution context for futures below
val log = Logging(system, getClass)
log.info("Requesting the elevation of Mt. Everest from Googles Elevation API...")
val pipeline = sendReceive ~> unmarshal[GoogleApiResult[Elevation]]
val responseFuture = pipeline {
Get("http://maps.googleapis.com/maps/api/elevation/json?locations=27.988056,86.925278&sensor=false")
}
responseFuture onComplete {
case Success(GoogleApiResult(_, Elevation(_, elevation) :: _)) =>
log.info("The elevation of Mt. Everest is: {} m", elevation)
shutdown()
case Success(somethingUnexpected) =>
log.warning("The Google API call was successful but returned something unexpected: '{}'.", somethingUnexpected)
shutdown()
case Failure(error) =>
log.error(error, "Couldn't get elevation")
shutdown()
}
def shutdown(): Unit = {
IO(Http).ask(Http.CloseAll)(1.second).await
system.shutdown()
}
}
As it stands, this works perfectly and it prints the height of Mt.Everest as expected.
The strange thing happens if I move the file down one level in the package structure, that is I create a mypackage.myinnerpackage and move the file inside it.
IDEA changes my first line of code into package mypackage.myinnerpackage and that's it.
Then I try to run the app and the compilation will fail with the following message:
could not find implicit value for evidence parameter of type spray.httpx.unmarshalling.FromResponseUnmarshaller[courserahelper.sprayexamples.GoogleApiResult[courserahelper.sprayexamples.Elevation]]
val pipeline = sendReceive ~> unmarshal[GoogleApiResult[Elevation]]
^
I did not change anything in the code, I effectively just changed the package! Additionally, this code is self contained, it does not rely on any other implicit I declared in any other part of my code....
What am I missing?
Thanks!
(Replaced the comment by this answer which supports proper formatting.)
The code you posted is missing these two imports before the usage of unmarshal:
import ElevationJsonProtocol._
import SprayJsonSupport._
val pipeline = sendReceive ~> unmarshal[GoogleApiResult[Elevation]]
which exist in the original code. IntelliJ is sometimes messing with imports so that may be the reason they got lost in the transition.
You need to provide a Json Formatter for your case class.
case class Foo(whatever: Option[String])
object FooProtocol extends DefaultJsonProtocol {
implicit val fooJsonFormat = jsonFormat1(Foo)
}
Then include the following near the implementation...
import SprayJsonSupport._
import co.xxx.FooProtocol._

spray-json cannot marshal Map[String,String]

I have the following route setup, but when my map is returned in the first complete block I get an error:
could not find implicit value for evidence parameter of type spray.httpx.marshalling.Marshaller[scala.collection.immutable.Map[String,String]]
import spray.routing.HttpService
import akka.actor.Actor
import spray.http.HttpRequest
import spray.routing.RequestContext
import spray.json.DefaultJsonProtocol._
class UserServiceActor extends Actor with RestUserService {
def actorRefFactory = context
def receive = runRoute(linkRoute)
}
trait RestUserService extends HttpService {
val userService = new LinkUserService
def linkRoute =
pathPrefix("user" / Segment) {
userId =>
path("link") {
parameters('service ! "YT") {
complete {
Map("status"-> "OK", "auth_url" -> "http://mydomain.com/auth")
}
}
}
}
}
According to this test I should be able to convert a Map to json when DefaultJsonProtocol._ is imported but even that's failing:
val map:Map[String, String] = Map("hi"->"bye")
map.toJson
Cannot find JsonWriter or JsonFormat type class for scala.collection.mutable.Map[String,String]
Not sure what's wrong :(
Someone on the spray mailing list pointed out that the Map being created was a mutable one, spray-json won't marshal that. I changed it to be scala.collection.immutable.Map and also added the following imports:
import spray.httpx.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
And now everything works great.