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

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

Related

How to fix ZIO server Internal Server Error 500 generated buy custom method

I have this 2 methods
import org.h2.store.fs.FilePath
import zio.*
import zio.Console.printLine
import zio.http.Client
import zio.nio.file.*
import zio.nio.charset.Charset
import zio.stream.*
import java.io.IOException
object FileStorage:
def saveToFile(data: String = "", filePath: String = "src/main/resources/data.json"): Unit =
lazy val logic = for {
encoded <- Charset.Standard.utf8.encodeString(data)
path = Path(filePath.split("/").head, filePath.split("/").tail: _*)
notExists <- Files.notExists(path)
- <- if (notExists) Files.createFile(path) else ZIO.attempt(())
_ <- Files.writeBytes(path, encoded)
_ <- Console.printLine(s"written to $path")
} yield ()
def unsafeF = (unsafeVal: Unsafe) => {
implicit val unsafe: Unsafe = unsafeVal
Runtime.default.unsafe.run(logic)
}
Unsafe.unsafe(unsafeF)
def readFromFile: ZIO[Any, Throwable, String] = {
val path = Path("src", "main", "resources", "data.json")
val bool = for bool <- Files.isReadable(path) yield bool
val zioStr = bool.flatMap(bool =>
if (bool) Files.readAllLines(path, Charset.Standard.utf8).map(fileLines => fileLines.head)
else {
saveToFile()
readFromFile})
zioStr
}
In def readFromFile i try to make empty an empty file if file don't exists
File generation working fine
then I'm trying to read that empty file and return it like a ZIO Response like that
import zio.http.{Client, *}
import zio.json.*
import zio.http.model.Method
import zio.{Scope, Task, ZIO, ZIOAppDefault}
import zio.http.Client
import zhttp.http.Status.NotFound
import zhttp.http.Status
import scala.language.postfixOps
import zio._
import scala.collection.immutable.List
import zio.{ExitCode, URIO, ZIO}
object ClientServer extends ZIOAppDefault {
val app: Http[Client, Throwable, Request, Response] = Http.collectZIO[Request]
case Method.GET -> !! / "readLeagues" =>
FileStorage.readFromFile.map(str => Response.json(str))
BUT in this case I getting Internal Server Error 500 on postman in http://localhost:8080/readLeagues
If at first I feed prefilled json file to
def readFromFile
It works fine Status: 200
And I getting a nice looking json as a body
Maybe I should set another default strings for data to prefill
def saveToFile
so json can be parsable?
or smth else

scala akka http type mismatch

I created a project with open api then added a endpoint GET /product which is supposed to return a product( i am testing it so I just want it to return any product).
When I run the project I get the following error.
[error] found : org.openapitools.server.model.Product.type
[error] required: (?, ?, ?) => ?
[error] def toEntityMarshallerProduct: ToEntityMarshaller[Product] = jsonFormat3(Product)
the logs point at Product inside jsonFormat3
I noticed it's caused by the number of properties of the Product Model, if I reduce them to 3, it works ! this is weird! does anyone know how to resolve this?
this is the product model file
package org.openapitools.server.model
final case class Product (
id: Int,
name: String,
isAvailable: Boolean,
description: String,
category: String
)
this is the productAPI file
package org.openapitools.server.api
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
import org.openapitools.server.AkkaHttpHelper._
import org.openapitools.server.model.Product
class ProductApi(
productService: ProductApiService,
productMarshaller: ProductApiMarshaller
) {
import productMarshaller._
lazy val route: Route =
path("product" / "all") {
get {
productService.productAllGet()
}
}
}
trait ProductApiService {
def productAllGet200(responseProduct: Product)(implicit toEntityMarshallerProduct: ToEntityMarshaller[Product]): Route =
complete((200, responseProduct))
def productAllGet()(implicit toEntityMarshallerProduct: ToEntityMarshaller[Product]): Route
}
trait ProductApiMarshaller {
implicit def toEntityMarshallerProduct: ToEntityMarshaller[Product]
}
and this is the main file
import akka.actor.typed.{ActorSystem, ActorRef}
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
// for JSON serialization/deserialization following dependency is required:
// "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import scala.io.StdIn
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.{ ExecutionContext, Future }
import org.openapitools.server.api._
import org.openapitools.server.model._
object Main extends App {
// needed to run the route
implicit val system = ActorSystem(Behaviors.empty, "product")
// implicit val materializer = ActorMaterializer()
// needed for the future map/flatmap in the end and future in fetchItem and saveOrder
implicit val executionContext = system.executionContext
object DefaultMarshaller extends ProductApiMarshaller {
def toEntityMarshallerProduct: ToEntityMarshaller[Product] = jsonFormat3(Product)
}
object DefaultService extends ProductApiService {
def productAllGet() (implicit toEntityMarshallerProduct: ToEntityMarshaller[Product]) : Route = {
val reponse = Future {
Product(1,"product",false,"desc","pizza")
}
requestcontext => {
(reponse).flatMap {
(product: Product) =>
productAllGet200(product)(toEntityMarshallerProduct)(requestcontext)
}
}
}
}
val api = new ProductApi(DefaultService, DefaultMarshaller)
val host = "localhost"
val port = 3005
val bindingFuture = Http().newServerAt(host, port).bind(pathPrefix("api"){api.route})
println(s"Server online at http://${host}:${port}/\nPress RETURN to stop...")
bindingFuture.failed.foreach { ex =>
println(s"${ex} Failed to bind to ${host}:${port}!")
}
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
I noticed it's caused by the number of properties of the Product Model, if I reduce them to 3, it works ! this is weird! does anyone know how to resolve this?
try it with using a Data Transfer Object (DTO) which will provide by your api and a domain model object which is handled internally.
so you will use jsonFormat3 for your DTO and have no need to jsonFormat5.
a simple mapper or helper method inner of case classes provide a smooth integration of that.
for example:
final case class Product (
id: Int,
name: String,
isAvailable: Boolean,
description: String,
category: String
) {
def toDto: ProductDTO = ProductDTO(prop0, prop1, prop2)
}
final case class ProductDTO (
prop0: X,
prop1: Y,
prop2: Z
) {
def toDomain(propA: X, propB: Y): Product = Product(id, name, isAvailable, description, category)
}
object ProductSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val productFormat = json3Format(ProductDTO)
}

scala Spray Rest API return Json content type with RequestContext

My spray rest service invokes other actors(passing RequestContext in constructor) to perform business logic(similar to this approach). I have a use case where I need to read json text from a file and return the content. I want the content type to be JSON. How do I set content type to json explictly with requestcontext.
In the code snippet below requestContext needs to return string(Json) with Json content type. requestContext.complete("{\"name\":\"John\"}")
package com.christophergagne.sprayapidemo
import akka.actor.{Actor, ActorRef}
import akka.event.Logging
import akka.io.IO
import spray.routing.RequestContext
import spray.httpx.SprayJsonSupport
import spray.client.pipelining._
import scala.util.{ Success, Failure }
object TimezoneService {
case class Process(long: Double, lat: Double, timestamp: String)
}
class TimezoneService(requestContext: RequestContext) extends Actor {
import TimezoneService._
implicit val system = context.system
import system.dispatcher
val log = Logging(system, getClass)
def receive = {
case Process(long,lat,timestamp) =>
process(long,lat,timestamp)
context.stop(self)
}
def process(long: Double, lat: Double, timestamp: String) = {
log.info("Requesting timezone long: {}, lat: {}, timestamp: {}", long, lat, timestamp)
import TimezoneJsonProtocol._
import SprayJsonSupport._
val pipeline = sendReceive ~> unmarshal[GoogleTimezoneApiResult[Timezone]]
val responseFuture = pipeline {
Get(s"https://maps.googleapis.com/maps/api/timezone/json?location=$long,$lat&timestamp=$timestamp&sensor=false")
}
responseFuture onComplete {
case Success(GoogleTimezoneApiResult(_, _, timeZoneName)) =>
log.info("The timezone is: {} m", timeZoneName)
***requestContext.complete("{\"name\":\"John\"}")***
case Failure(error) =>
requestContext.complete(error)
}
}
}
thank you for help.
If you need to make your reply an application/json, you should use something like:
respondWithMediaType(MediaTypes.`application/json`) {
complete { ...
}
}
You need to return a full HttpResponse object instead of a simple string. I recommend you do this:
import spray.routing.RequestContext
import spray.http._
requestContext.complete(HttpResponse(StatusCodes.OK, HttpEntity(ContentType(MediaTypes.`application/json`), "{\"name\":\"John\"}")))
You could also return only the HttpEntity since it has a defined ToResponseMarshaller which can be found here. Use it like so:
import spray.routing.RequestContext
import spray.http._
requestContext.complete(HttpEntity(ContentType(MediaTypes.`application/json`), "{\"name\":\"John\"}"))
I don't recommend you use string interpolation to return your JSON since this makes it hard to change the response structure. I recommend you use the spray-json library which already has a ToReponseMarshaller defined which can be found here. The functionality is documented here. Your code would look something like this:
import spray.routing.RequestContext
import spray.httpx.marshalling._
import spray.json._
import spray.httpx.SprayJsonSupport._
requestContext.complete(JsObject("name" -> JsString("John")))

reactivemongo, could not find implicit value for parameter reader

I'm doing tests with reactivemongo
In my controller I have this:
package controllers
import models._
import models.JsonFormats._
import play.modules.reactivemongo.MongoController
import scala.concurrent.Future
import reactivemongo.api.Cursor
import org.slf4j.{LoggerFactory, Logger}
import javax.inject.Singleton
import play.api.mvc._
import reactivemongo.api.collections.default.BSONCollection
import reactivemongo.bson._
#Singleton
class Users extends Controller with MongoController {
private final val logger: Logger = LoggerFactory.getLogger(classOf[Users])
val collection = db[BSONCollection]("users")
// list all articles and sort them
def list = Action.async { implicit request =>
// get a sort document (see getSort method for more information)
val sort = getSort(request)
// build a selection document with an empty query and a sort subdocument ('$orderby')
val query = BSONDocument(
"$orderby" -> sort,
"$query" -> BSONDocument())
val activeSort = request.queryString.get("sort").flatMap(_.headOption).getOrElse("none")
// the cursor of documents
val found = collection.find(query).cursor[User]
// build (asynchronously) a list containing all the articles
found.collect[List]().map { users =>
Ok(views.html.admin.list(users, activeSort))
}.recover {
case e =>
e.printStackTrace()
BadRequest(e.getMessage())
}
}
...........
}
and in my model i have this:
package models
import reactivemongo.bson._
case class User(
nickName: String,
email: String,
password: String,
active: Boolean
)
object JsonFormats {
import play.api.libs.json.Json
// Generates Writes and Reads for Feed and User thanks to Json Macros
implicit val userFormat = Json.format[User]
}
When I compile the project returns the following error:
could not find implicit value for parameter reader: reactivemongo.bson.BSONDocumentReader[models.User]
in this line is the problem:
val found = collection.find(query).cursor[User]
Can anyone tell me where I'm wrong or what I'm missing please?
You have no implicit handler defined to map your model class to a BSONDocument. You can implement it yourself, or, just like you did for the JsonFormats, you could use the macros provided by ReactiveMongo.
object BsonFormats {
import reactivemongo.bson.Macros
implicit val userFormat = Macros.handler[User]
}
Alternatively, instead of the BSONCollection, you could use the JSONCollection provided by Play-ReactiveMongo to perform your mapping using the JSON format that you have already defined.
For me, I still get the error even after I have declared the implicits for both bson and json format. What I need to do is just import this:
import reactivemongo.api.commands.bson.BSONCountCommandImplicits._

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.