I'm fumbling my way through akka-http; I've got a single route that compiles:
val route = get {
pathPrefix("compass") {
path("route") {
parameters(('srclat.as[Double], 'srclon.as[Double],
'destlat.as[Double], 'destlon.as[Double])) {
(srclat, srclon, destlat, destlon) =>
complete(getRoute((LatLong(srclat, srclon),
LatLong(destlat, destlon))))
}
}
}
}
And I've verified that the parameters are being extracted correctly. When I call the method (with valid lat / longs), I'm expecting to receive an array of coordinates representing a (physical) route, but instead I receive a route object with an empty list of coordinates. Here's the signature of the method being run by complete:
// the Route here is a domain object, not an akka-http Route
def getRoute(coordinates: (LatLong, LatLong)):
Future[Option[Route]] = ???
And starting the server itself looks something like this:
val bindingFuture = Http().bindAndHandle(service.route, "0.0.0.0",
port)
I'm using akka and akka-streams 2.5.4 and akka-http 10.0.9, with Circe support from hseeberger (version 1.18.0). If anyone knows what I'm doing wrong here, please let me know...any help would be appreciated!
instead I receive a route object with an empty list of coordinates.
I think the problem is not in the code shown, but somewhere inside the getRoute function.
I have a hunch that you might be making changes to an immutable case class, and returning a previous copy, rather than the updated version?
e.g. code like the following would give the bug you describe:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
trait LatLong
case class Route(steps: List[String]) // for example
def getRoute(coordinates: (LatLong, LatLong)): Future[Option[Route]] = {
val myRoute = new Route(Nil)
val steps = List("one", "two", "three")
myRoute.copy(steps = steps) // BUG HERE, new copy discarded
Future(Some(myRoute))
}
If that doesn't explain things, please could you show more of the getRoute function?
A quick test to narrow things down might be to change getRoute temporarily to return a hardcoded non-empty Route, and check that it comes back OK over HTTP.
This was completely my fault; due to a cut-and-paste error, the source coordinates were being sent in as both source and destination, so the empty Route object was legitimate. Thanks to those who took their time looking into my screw-up!
Related
I have service that returns a ZIO[Has[MyCustomHeader]], and I'm having trouble testing it.
Other services in our organisation are tested by converting ZIO to Twitterfuture using runtime.unsafeRunToFuture (where runtime is a Runtime[ZEnv] ) and then awaiting the future, thus running the tests in blocking mode.
However this service has a Has[] requirement and runtime.unsafeRunToFuture doesnt handle those. So far my approach has been to try to convert my ZIO[Has[MyCustomHeader]] to a ZIO[ZEnv], but I've yet to succeed at this.
from what I gather I need to provide a ZLayer via ZIO.provideSomeLayer() but I'm simply too stupid to understand how to construct a ZLayer properly?
Am I even on the right path here? and if so, How do I construct a ZLayer with a static value for MyCustomHeader to use in my tests?
This is how far along I am at trying to add a header for testing purposes: it doesn't work, but might illustrate what I'm trying to achieve..maybe... I'm pretty confused myself:
object effectAwait {
implicit class ZioEffect[A](private val value: ZIO[Has[EnvironmentHeader], RequestFailure, A]) extends AnyVal {
final def await(implicit runtime: Runtime[ZEnv] = Runtime.default): A = {
val zmanaged = ZManaged.fromEffect(value).provide(Has(EnvironmentHeader("test")))
val layered = value.provideSomeLayer(zmanaged.toLayer)
val sf = runtime.unsafeRunToFuture(layered)
Await.result(sf, 10.seconds)
}
}
}
this however gives me the error:
could not find implicit value for izumi.reflect.Tag[A]. Did you
forget to put on a Tag, TagK or TagKK context bound on one of the
parameters in A? e.g. def x[T: Tag, F[_]: TagK] = ...
<trace>:
deriving Tag for A, dealiased: A:
could not find implicit value for Tag[A]: A is a type parameter without an implicit Tag!
val layered = value.provideSomeLayer(zmanaged.toLayer)
I think you can just use ZIO.provideLayer (instead of provideSomeLayer) here :)
Also, there's a runtime.unsafeRun that will wait for the result as well, so you don't necessarily have to convert it to a Future. Also, also, instead of relying on an implicit runtime, there's always zio.Runtime.default that you can use anywhere (it's a Runtime[ZEnv] so it should work just as well, unless you've otherwise customized the runtime's behavior)
I'm trying to convert a few endpoints I have to use concurrency. I have the following method for the controller:
Original method
def getHistory(id:String) = Action {
val response = historyService.getPersonHistory(id)
Ok(write(response)) as "application/json"
}
New Method
def getHistory(id:String) = Action.async {
val response = scala.concurrent.Future {
historyService.getPersonHistory(id)
}
response.map(i => Ok(i))
}
So, when we try this with a simple example process (not calling another method, but just calculating an Int) it seems to work. In the new version above, I keep getting the error:
"No implicits found for parameter writable: Writeable[historyResponse]
Cannot write an instance of models.HistoryResponse to HTTP response. Try to define a Writeable[models.HistoryResponse]"
I'm new to Scala, and having difficulty finding information on making writeables. What do I need to be able to return the results as before?
Thanks
You need to define an implicit val tjs: Writes[HistoryResponse] or even better, implicit val format: Format[HistoryResponse] = Json.format[HistoryResponse] in the companion object for HistoryResponse, so that play can auto convert your data to json. by the way, not a good name for i in the map function, something like "history" would be better instead of "i".
I am new to Scala and was trying my hands on with akka. I am trying to access data from MongoDB in Scala and want to convert it into JSON and XML format.
This code attached below is using path /getJson and calling getJson() function to get data in a form of future.
get {
concat(
path("getJson"){
val f = Patterns.ask(actor1,getJson(),10.seconds)
val res = Await.result(f,10.seconds)
val result = res.toString
complete(res.toString)
}
}
The getJson() method is as follows:
def getJson()= {
val future = collection.find().toFuture()
future
}
I have a Greeting Case class in file Greeting.scala:
case class Greeting(msg:String,name:String)
And MyJsonProtocol.scala file for Marshelling of scala object to JSON format as follows:
trait MyJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
implicit val templateFormat = jsonFormat2(Greeting)
}
I am getting output of complete(res.toString) in Postman as :
Future(Success(List(
Iterable(
(_id,BsonObjectId{value=5fc73944986ced2b9c2527c4}),
(msg,BsonString{value='Hiiiiii'}),
(name,BsonString{value='Ruchirrrr'})
),
Iterable(
(_id,BsonObjectId{value=5fc73c35050ec6430ec4b211}),
(msg,BsonString{value='Holaaa Amigo'}),
(name,BsonString{value='Pablo'})),
Iterable(
(_id,BsonObjectId{value=5fc8c224e529b228916da59d}),
(msg,BsonString{value='Demo'}),
(name,BsonString{value='RuchirD'}))
)))
Can someone please tell me how to iterate over this output and to display it in JSON format?
When working with Scala, its very important to know your way around types. First step toweards this is at least knowing the types of your variables and values.
If you look at this method,
def getJson() = {
val future = collection.find().toFuture()
future
}
Is lacks the type type information at all levels, which is a really bad practice.
I am assuming that you are using mongo-scala-driver. And your collection is actually a MongoCollection[Document].
Which means that the output of collection.find() should be a FindOberservable[Document], hence collection.find().toFuture() should be a Future[Seq[Document]]. So, your getJson method should be written as,
def getJson(): Future[Seq[Document]] =
collection.find().toFuture()
Now, this means that you are passing a Future[Seq[Document]] to your actor1, which is again a bad practice. You should never send any kind of Future values among actors. It looks like your actor1 does nothing but sends the same message back. Why does this actor1 even required when it does nothing ?
Which means your f is a Future[Future[Seq[Document]]]. Then you are using Await.result to get the result of this future f. Which is again an anti-pattern, since Await blocks your thread.
Now, your res is a Future[Seq[Document]]. And you are converting it to a String and sending that string back with complete.
Your JsonProtocol is not working because you are not even passing it any Greeting's.
You have to do the following,
Read raw Bson objects from mongo.
convert raw Bson objects to your Gretting objects.
comlete your result with these Gretting objects. The JsonProtocol should take case of converting these Greeting objects to Json.
The easist way to do all this is by using the mongo driver's CodecRegistreis.
case class Greeting(msg:String, name:String)
Now, your MongoDAL object will look like following (it might be missing some imports, fill any missing imports as you did in your own code).
import org.mongodb.scala.bson.codecs.Macros
import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.bson.codecs.configuration.CodecRegistries
import org.mongodb.scala.{MongoClient, MongoCollection, MongoDatabase}
object MongoDAL {
val greetingCodecProvider = Macros.createCodecProvider[Greeting]()
val codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromProviders(greetingCodecProvider),
DEFAULT_CODEC_REGISTRY
)
val mongoClient: MongoClient = ... // however you are connecting to mongo and creating a mongo client
val mongoDatabase: MongoDatabase =
mongoClient
.getDatabase("database_name")
.withCodecRegistry(codecRegistry)
val greetingCollection: MongoCollection[Greeting] =
mongoDatabase.getCollection[Greeting]("greeting_collection_name")
def fetchAllGreetings(): Future[Seq[Greeting]] =
greetingCollection.find().toFuture()
}
Now, your route can be defined as
get {
concat(
path("getJson") {
val greetingSeqFuture: Future[Seq[Greeting]] = MongoDAL.fetchAllGreetings()
// I don't see any need for that actor thing,
// but if you really need to do that, then you can
// do that by using flatMap to chain future computations.
val actorResponseFuture: Future[Seq[Greeting]] =
greetingSeqFuture
.flatMap(greetingSeq => Patterns.ask(actor1, greetingSeq, 10.seconds))
// complete can handle futures just fine
// it will wait for futre completion
// then convert the seq of Greetings to Json using your JsonProtocol
complete(actorResponseFuture)
}
}
First of all, don't call toString in complete(res.toString).
As it said in AkkaHTTP json support guide if you set everything right, your case class will be converted to json automatically.
But as I see in the output, your res is not an object of a Greeting type. Looks like it is somehow related to the Greeting and has the same structure. Seems to be a raw output of the MongoDB request. If it is a correct assumption, you should convert the raw output from MongoDB to your Greeting case class.
I guess it could be done in getJson() after collection.find().
My attempt so far ran into java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to scala.Option. In fact I even can not think how the appropriate response may look like. Is it possible?
Here could be two options: either it's a bug in your program or you didn't provide spray any way to marshall your Foo type, cause Future and Option are handled by default. E.g this route can be handled by standard spray marshallers without any problems:
val route = {
(get & path("test")) {
complete {
Future(Option("Hello Spray!"))
}
}
}
Now if you make a GET request on /test you'll get a correct response.
If you have a specific type, then you need to provide you own marshaller of type ToResponseMarshallable to sray through implicit context. I think the most common and easiest way would be to make a Json response, for this you need a spray-json (or some other supported json lib) and just provide a converter to json, like:
import spray.json.DefaultJsonProtocol._
case class Boy(name: String, age: String)
object Boy {
implicit val boyJson = jsonFormat2(Boy.apply)
}
Now the only thing left to do is place a json marshaller into the scope:
import spray.httpx.SprayJsonSupport._ // import json marshaller
val route = {
(get & path("test") {
complete {
Future(Option(Boy("Name", 0)))
}
}
}
Now you'll get a json response. IF you need some other kind of response, make a custom marshaller for a Boy type.
In short - Yes, but you need to make compiler happy.
Spray lets you return Future or plain response as long as it can be marshalled back to a response. You need to make sure that you either have implicit conversions in the scope that do the marshalling for you or transform your Foo object explicitly.
Here is how response transformation is performed: http://spray.io/documentation/1.2.0/spray-httpx/response-transformation/.
To anyone still getting issues returning a future in the complete block, make sure to have an execution context such as scala.concurrent.ExecutionContext.Implicits.global in scope! IDE tends to not understand this is the problem and can send you down a long rabbit hole.
As far as I know, we can't do this in Java. Can we do this in scala?
Suppose there is a method as following:
def insert(name:String, age:Int) {
// insert new user
}
Is it possible to get the parameter names name and age in scala?
UPDATE
I want to do this, because I want to bind the parameters of methods automaticlly.
For example, this is a web app, it has some actions defined as:
class UsersController extends Controller {
def create(name: String, age: Int) {
// insert the user
}
}
A client clicked the submit button of a creating-user form. The url will be /users/create and with some parameters sending.
On the server side, when we get a url named /users/create, we will find a method create in the controller UsersController, found one now. Then I have to get the parameter names of that method, then we can get the values of them:
val params = getParamsFromRequest()
val method = findMethodFromUrl("/users/create")
val paramNames = getParamNamesOfMethod(method)
val paramValues = getValues(params, paramNames)
// then invoke
method.invoke(controller, paramValues)
Now, the key is how to get the parameter names of a method?
It's still very much a work in progress, but you should be able to:
import scalaj.reflect._
for {
clazz <- Mirror.ofClass[UsersController].toSeq
method <- clazz.allDefs.find(_.name == "insert").toSeq
params <- method.flatParams
} yield params
Sorry about the toSeqs, they're to work around a known issue using Options in a for-comprehension.
Please don't push it too hard, it's still very young and fragile. I'm certainly not at the stage yet where I'd accept bug reports :)
UPDATE
Well... I know that I said I'm not accepting bug reports, but this one seemed so useful that I've pushed it up to github anyway.
To do the job dynamically (from a String class name):
import scalaj.reflect._
for {
clazz <- Option(Class forName "x.y.UsersController")
mirror <- Mirror.ofClass(clazz).toSeq
method <- mirror.allDefs.find(_.name == "insert").toSeq
params <- method.flatParams
} yield params
This question has some insight on how it is done in Java: Is there a way to obtain names of method parameters in Java?
Why not just using different method names (to reflect the parameters that may be passed)? Like, craeteWithNameAndAgeAndGender (pretty standard approach). You won't anyways be able to have multiple methods with the same names(/arity/parameter types and order) and just different parameter names - method overloading doesn't work this way.