I want to have a way access certain query parameters for all of my requests. An example query would be something like:
http://api.mysite.com/accounts/123?include=friends,photos
Where I want to get access to the comma separated list of include relationships.
As far as I could tell, the following doesn't work and will look at the include list as a single string:
// routes.txt
GET /accounts/:id controllers.AccountsController.get(id: Int, include: Seq[String])
This is how I am currently doing it, but I was hoping there was a cleaner way.
// routes.txt
GET /accounts/:id controllers.AccountsController.get(id: Int, include: Option[String])
// AccountsController.scala
def get(id: Int, include: Option[String]) = Action {
// Convert the option to a set
val set = if (include.isDefined) include.get.split(",").toSet else Set()
}
The proper way to do it (already supported by Play) would be to use repeated key-values in the query string, i.e.:
http://api.mysite.com/accounts/123?include=friends&include=photos
That would automatically bind Seq("friends", "photos") to include in that route. This has the advantage of being able to use commas within keys, and is consistent with the common usage of query string parameters.
Alternatively, you can create a custom QueryStringBindable[List[String]] that can handle a comma-separated list. Something like:
object QueryStringBinders {
implicit def listBinder(key: String)(implicit stringBinder: QueryStringBindable[String]) = {
new QueryStringBindable[List[String]] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, List[String]]] =
stringBinder.bind(key, params).map(_.right.map(_.split(",").toList))
override def unbind(key: String, strings: List[String]): String =
s"""$key=${strings.mkString(",")}"""
}
}
}
Then you would use PlayKeys.routesImport += "QueryStringBinders._" within build.sbt to use it (or whatever the fully qualified package name is). Using a QueryStringBindable would make the split logic reusable, with minimal boilerplate.
As, #m-z said the proper way to use is repeated key-values in query string like: http://api.mysite.com/accounts/123?include=friends&include=photos
and in you action you can access query string with queryString method as well i.e
your route will look like this:
GET /accounts/:id controllers.AccountsController.get(id: Int)
and in your controller:
// AccountsController.scala
def get(id: Int) = Action { request =>
// the `request.queryString` will give you Map[String, Seq[String]] i.e all they keys and their values
val includes = request.queryString.getOrElse("include", Nil)
}
Related
I'm using CaffeineCache together with memoizeF to cache the result of an operation that takes a case class as an input, like this:
case class Foo(id: UUID, bar: String)
implicit val myCache: CaffeineCache[Foo] =
buildCache(cacheConfig.size) //builds the CaffeineCache
def cachedOperation(foo: Foo): Future[Foo] =
memoizeF(cacheConfig.duration) {
// do something with foo and return it
}
Now, in some cases I need to explicitly delete a specific element from the cache.
I think I could use myCache.doRemove(key) but from what I see in the ScalaCache documentation, when using memoizeF the key will be generated "from the class name, the name of the enclosing method, and the values of all of the method’s parameters" and I don't think backwards-engineering key from that and using it with doRemove is a good idea.
Is there another way of removing a specific element from the cache, if it was inserted using memoizeF? Or perhaps, could I in some way tell memoizeF to use id from Foo as the key instead of generating it from the class name etc (I know for certain that ids will be unique for every Foo instance)?
Perhaps using memoizeF is not a good idea at all for this scenario and I should go back to inserting to the cache "manually"? Any input is appreciated. I've looked in the official documentation and googled around but with no luck.
Cache[F[_], V] trait has explicit caching method where you can specify key for cache: https://github.com/cb372/scalacache/blob/master/modules/core/src/main/scala/scalacache/CacheAlg.scala#L56
So you can do:
case class Foo(id: UUID, bar: String)
val myCache: CaffeineCache[Foo] = buildCache(cacheConfig.size) //builds the CaffeineCache
def cachedOperation(foo: Foo): Future[Foo] =
myCache.caching(foo)(cacheConfig.duration) {
// do something with foo and return it
}
def removeCache(foo: Foo): Future[Unit] = myCache.remove(foo)
I'm trying to unmarshal parameters from an input sent to my route so that the route is able to deal with the parameters and send back a BadRequest if the parameters are not correctly formatted. The specific parameter I am looking at parsing is an orderBy request used in a REST-ful application.
I was thinking of structuring the request by having multiple fields coupled with a sorting integer. The field and integer are separated by a : whereas the field-integer combinations are comma separated.
An example of this request would look like this: /path?orderBy=id:-1,name:1
I would like to create a custom unmarshaller that is able to turn this parameter list into a List[(String, Int)] to send to my query function that will decompose it and use it in the query. Akka-http supports a CsvList unmarshaller however this is not adequate for my implementation.
Based on the answer provided by Tim I was able to come up with a slightly different answer. The original answer by Tim has an unnecessary implicit parameter inside the definition which can be removed since this implementation doesn't require an unmarshaller of its own. It is a base unmarshaller.
I did this:
implicit val pairSeq = Unmarshaller.strict[String, (String, Int)] { string =>
val Array(a, b) = string.split(':')
a -> b.toInt
}
...
import foo.pairSeq
path("PATH") {
parameter("orderBy".as(CsvSeq[(String, Int)])) { ob =>
...
}
This should work:
implicit def pairSeq(implicit unmarshaller: Unmarshaller[String, (String, Int)]) =
Unmarshaller.strict[String, (String, Int)] { string =>
val Array(a, b) = string.split(':')
a -> b.toInt
}
...
path("PATH") {
parameter("orderBy".as(CsvSeq[(String, Int)])) { ob =>
...
}
ob will be a List[(String, Int)] if the parameter is formatted correctly.
This uses a custom marshaller for (String, Int) which splits the string at : and converts the second value to Int. This custom marshaller is used by CsvSeq to unpick each comma-separated value in orderBy.
I'm learning play framework by my self.
What is the difference between a query string and a path parameter in routers? and what is the difference between PathBindable and QueryStringBindable ?
It's described in the documentation:
https://www.playframework.com/documentation/2.5.x/ScalaRouting
https://www.playframework.com/documentation/2.5.x/ScalaRequestBinders
In a short, path parameter is a parameter in the URL:
# Client, like /clients/125
GET /clients/:id controllers.Clients.show(id: Long)
Query string parameters are query string parameters:
# Pagination links, like /clients?page=3
GET /clients controllers.Clients.list(page: Int ?= 1)
You can see that in this example Play binds parameters from string to other types, like Long and Int. There are situations when you want to bind parameters to other types, then you need to write your own binder. PathBindable for path parameter and QueryStringBindable for query parameters.
So, if you can bind client id to client you can write
# Client, like /clients/125
GET /clients/:id controllers.Clients.show(client: Client)
And the binder:
implicit def pathBinder(implicit intBinder: PathBindable[Int]) = new PathBindable[Client] {
override def bind(key: String, value: String): Either[String, Client] = {
for {
id <- intBinder.bind(key, value).right
client <- Client.findById(id).toRight("Client not found").right
} yield client
}
override def unbind(key: String, client: Client): String = {
client.id.toString
}
}
If you want to have full control over URL parsing and binding, you can go even further and use String Interpolating Routing DSL
We are building some sync functionality using two-way json requests and this algorithm. All good and we have it running in prototype mode. Now I am trying to genericise the code, as we will be synching for several tables in the app. It would be cool to be able to define a class as "extends Synchable" and get the additional attributes and sync processing methods with a few specialisations/overrides. I have got this far:
abstract class Synchable [T<:Synchable[T]] (val ruid: String, val lastSyncTime: String, val isDeleted:Int) {
def contentEquals(Target: T): Boolean
def updateWith(target: T)
def insert
def selectSince(clientLastSyncTime: String): List[T]
def findByRuid(ruid: String): Option[T]
implicit val validator: Reads[T]
def process(clientLastSyncTime: String, updateRowList: List[JsObject]) = {
for (syncRow <- updateRowList) {
val validatedSyncRow = syncRow.validate[Synchable]
validatedSyncRow.fold(
valid = { result => // valid row
findByRuid(result.ruid) match { //- do we know about it?
case Some(knownRow) => knownRow.updateWith(result)
case None => result.insert
}
}... invalid, etc
I am new to Scala and know I am probably missing things - WIP!
Any pointers or suggestions on this approach would be much appreciated.
Some quick ones:
Those _ parameters you pass in and then immediately assign to vals: why not do it in one hit? e.g.
abstract class Synchable( val ruid: String = "", val lastSyncTime: String = "", val isDeleted: Int = 0) {
which saves you a line and is clearer in intent as well I think.
I'm not sure about your defaulting of Strings to "" - unless there's a good reason (and there often is), I think using something like ruid:Option[String] = None is more explicit and lets you do all sorts of nice monad-y things like fold, map, flatMap etc.
Looking pretty cool otherwise - the only other thing you might want to do is strengthen the typing with a bit of this.type magic so you'll prevent incorrect usage at compile-time. With your current abstract class, nothing prevents me from doing:
class SynchableCat extends Synchable { ... }
class SynchableDog extends Synchable { ... }
val cat = new SynchableCat
val dog = new SynchableDog
cat.updateWith(dog) // This won't end well
But if you just change your abstract method signatures to things like this:
def updateWith(target: this.type)
Then the change ripples down through the subclasses, narrowing down the types, and the compiler will omit a (relatively clear) error if I try the above update operation.
I've been reading about the OO 'fluent interface' approach in Java, JavaScript and Scala and I like the look of it, but have been struggling to see how to reconcile it with a more type-based/functional approach in Scala.
To give a very specific example of what I mean: I've written an API client which can be invoked like this:
val response = MyTargetApi.get("orders", 24)
The return value from get() is a Tuple3 type called RestfulResponse, as defined in my package object:
// 1. Return code
// 2. Response headers
// 2. Response body (Option)
type RestfulResponse = (Int, List[String], Option[String])
This works fine - and I don't really want to sacrifice the functional simplicity of a tuple return value - but I would like to extend the library with various 'fluent' method calls, perhaps something like this:
val response = MyTargetApi.get("customers", 55).throwIfError()
// Or perhaps:
MyTargetApi.get("orders", 24).debugPrint(verbose=true)
How can I combine the functional simplicity of get() returning a typed tuple (or similar) with the ability to add more 'fluent' capabilities to my API?
It seems you are dealing with a client side API of a rest style communication. Your get method seems to be what triggers the actual request/response cycle. It looks like you'd have to deal with this:
properties of the transport (like credentials, debug level, error handling)
providing data for the input (your id and type of record (order or customer)
doing something with the results
I think for the properties of the transport, you can put some of it into the constructor of the MyTargetApi object, but you can also create a query object that will store those for a single query and can be set in a fluent way using a query() method:
MyTargetApi.query().debugPrint(verbose=true).throwIfError()
This would return some stateful Query object that stores the value for log level, error handling. For providing the data for the input, you can also use the query object to set those values but instead of returning your response return a QueryResult:
class Query {
def debugPrint(verbose: Boolean): this.type = { _verbose = verbose; this }
def throwIfError(): this.type = { ... }
def get(tpe: String, id: Int): QueryResult[RestfulResponse] =
new QueryResult[RestfulResponse] {
def run(): RestfulResponse = // code to make rest call goes here
}
}
trait QueryResult[A] { self =>
def map[B](f: (A) => B): QueryResult[B] = new QueryResult[B] {
def run(): B = f(self.run())
}
def flatMap[B](f: (A) => QueryResult[B]) = new QueryResult[B] {
def run(): B = f(self.run()).run()
}
def run(): A
}
Then to eventually get the results you call run. So at the end of the day you can call it like this:
MyTargetApi.query()
.debugPrint(verbose=true)
.throwIfError()
.get("customers", 22)
.map(resp => resp._3.map(_.length)) // body
.run()
Which should be a verbose request that will error out on issue, retrieve the customers with id 22, keep the body and get its length as an Option[Int].
The idea is that you can use map to define computations on a result you do not yet have. If we add flatMap to it, then you could also combine two computations from two different queries.
To be honest, I think it sounds like you need to feel your way around a little more because the example is not obviously functional, nor particularly fluent. It seems you might be mixing up fluency with not-idempotent in the sense that your debugPrint method is presumably performing I/O and the throwIfError is throwing exceptions. Is that what you mean?
If you are referring to whether a stateful builder is functional, the answer is "not in the purest sense". However, note that a builder does not have to be stateful.
case class Person(name: String, age: Int)
Firstly; this can be created using named parameters:
Person(name="Oxbow", age=36)
Or, a stateless builder:
object Person {
def withName(name: String)
= new { def andAge(age: Int) = new Person(name, age) }
}
Hey presto:
scala> Person withName "Oxbow" andAge 36
As to your use of untyped strings to define the query you are making; this is poor form in a statically-typed language. What is more, there is no need:
sealed trait Query
case object orders extends Query
def get(query: Query): Result
Hey presto:
api get orders
Although, I think this is a bad idea - you shouldn't have a single method which can give you back notionally completely different types of results
To conclude: I personally think there is no reason whatsoever that fluency and functional cannot mix, since functional just indicates the lack of mutable state and the strong preference for idempotent functions to perform your logic in.
Here's one for you:
args.map(_.toInt)
args map toInt
I would argue that the second is more fluent. It's possible if you define:
val toInt = (_ : String).toInt
That is; if you define a function. I find functions and fluency mix very well in Scala.
You could try having get() return a wrapper object that might look something like this
type RestfulResponse = (Int, List[String], Option[String])
class ResponseWrapper(private rr: RestfulResponse /* and maybe some flags as additional arguments, or something? */) {
def get : RestfulResponse = rr
def throwIfError : RestfulResponse = {
// Throw your exception if you detect an error
rr // And return the response if you didn't detect an error
}
def debugPrint(verbose: Boolean, /* whatever other parameters you had in mind */) {
// All of your debugging printing logic
}
// Any and all other methods that you want this API response to be able to execute
}
Basically, this allows you to put your response into a contain that has all of these nice methods that you want, and, if you simply want to get the wrapped response, you can just call the wrapper's get() method.
Of course, the downside of this is that you will need to change your API a bit, if that's worrisome to you at all. Well... you could probably avoid needing to change your API, actually, if you, instead, created an implicit conversion from RestfulResponse to ResponseWrapper and vice versa. That's something worth considering.