Routes overloading doesn't work - scala

I want to be able to have this:
POST /items controllers.Application.update()
POST /items/:itemType controllers.Application.update(itemType: String)
POST /items/:itemType/:id controllers.Application.update(itemType: String, id: Int)
but that doesn't compile due to the error of method update is defined twice. Then I changed it and it didn't compiler either:
POST /items controllers.Application.update(itemType: Option[String] = None, id: Option[Int] = None)
POST /items/:itemType controllers.Application.update(itemType: String, id: Option[Int] = None)
POST /items/:itemType/:id controllers.Application.update(itemType: String, id: Int)
the errors are:
the previouse one
and type mismatch; found: Option[String]; required: String
What do I do about that? I wouldn't like to do something like this:
POST /items controllers.Application.updateAll()
POST /items/:itemType controllers.Application.updateByType(itemType: String)
POST /items/:itemType/:id controllers.Application.updateByTypeAndId(itemType: String, id: Int)
and this doesn't look good either since I'd like to use Option instead of the empty string:
POST /items controllers.Application.update(itemType: String = "", id: Int = "")
POST /items/:itemType/:id controllers.Application.update(itemType: String, id: Int = "")
POST /items/:itemType/:id controllers.Application.update(itemType: String, id: Int)

Unfortunately it seems support for Option was removed in v2 - see here for example - so you may be stuck with either coding your own PathBindable to handle Options (as mentioned in the above link), or one of the other unsavoury choices you've noted.

If you're able to change your URL format, you have the ability to use Option.
Route: POST /items controllers.Application.update(itemType: Option[String], id: Option[Int])
URL: http://localhost:9000/items?itemType=someItem&id=123
With this format, you are able to omit itemType, id, or both when making the web service call.

Related

Scala: how to selectively write fields of an object to json string?

I have a query object:
case class SearchQuery(keyword: String, count: Int, sort: String)
I serialize this object to send it to a restful api to get search response.
Is it possible to not write some of the properties when serializing based on some condition like, if sort is empty I want the json string to be "{keyword: 'whatever', count: 25}" and if sort is non empty then I would like it to be "{keyword: 'whatever', count: 25, sort: 'unitPrice'}". What is best way to achieve this?
I am using lift json for serialization.
Any help is greatly appreciated. Thank you.
Update
val reqHeaders: scala.collection.immutable.Seq[HttpHeader] = scala.collection.immutable.Seq(
RawHeader("accept", "application/json"),
RawHeader("authorization", "sgdg545wf34rergt34tg"),
RawHeader("content-type", "application/json"),
RawHeader("x-customer-id", "45645"),
RawHeader("x-locale-currency", "usd"),
RawHeader("x-locale-language", "en"),
RawHeader("x-locale-shiptocountry", "US"),
RawHeader("x-locale-site", "us"),
RawHeader("x-partner-id", "45sfg45fgd5")
)
implicit val formats = DefaultFormats
val searchObject = net.liftweb.json.Serialization.write(req) //req is search object
val searchObjectEntity = HttpEntity(ContentTypes.`application/json`, searchObject)
val request = HttpRequest(HttpMethods.POST, "https://api.xxxxxxx.com/services/xxxxxxxx/v1/search?client_id=654685", reqHeaders, searchObjectEntity)
In Lift-Json, optional values are not serialized. So if you change your case class to have case class SearchQuery(keyword: String, count: Int, sort: Option[String]), you should get just the behavior you want.
See "Any value can be optional" in
https://github.com/lift/lift/tree/master/framework/lift-base/lift-json
You can make your your sort field optional, as scala gives you a way of handling fields which can be optional and then use Lift Json or Jerkson Json for serialization.
Here is the sample code with Jerkson Json.
case class SearchQuery(keyword: String, count: Int, sort: Option[String])
com.codahale.jerkson.Json.generate(SearchQuery("keyword",1,None))
This will give you output ->
{"keyword":"keyword","count":1}

Query parameter as List

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)
}

Map table with more than 22 columns to Scala case class by Slick 2.1.0

I'm using Scala 2.11, Slick 2.1.0-M2, PlayFramework 2.3.1.
I need to map 25 columns table to Scala's case class.
For example I have this case class:
case class Test(f1: Long, f2: String, f3: String, f4: String, f5: String,
f6: String, f7: String, f8: String, f9: String, f10: String,
f11: String, f12: String, f13: String, f14: String, f15: String,
f16: String, f17: String, f18: String, f19: String, f20: String,
f21: String, f22: String, f23: Float, f24: Float, f25: String)
I read that it is possible to write custom Shape (proof), but any my attempts to realize it is fails.
Please help me map this case class to table.
There is not a good solution to this question for current slick version. You can pack some fields into one case class.
Please refer to this test case.
https://github.com/slick/slick/blob/2.1.0-RC1/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/JdbcMapperTest.scala#L99
Actually this could be done via HList like this
def * = (col1 :: col2 :: .. :: HNil).shaped <> (
{ case x => new YYY(x(0), x(1), ..)},
{ x: YYY => Option(x.col1 :: x.col2 :: .. :: HNil)}
)
I've written an macro to do the mapping, you may take a look at this
https://github.com/jilen/slickext
I have a HListCaseClassShape that works exactly like the CaseClassShape but without the 22 column limit: here. You could then map it to your table like this: def * = MyHListCaseClassShape(f1, f2, f3...) (see the Pair * example here). Not sure if it'll work with Slick 2 but might be worth a shot.

Overriding methods seem don't work

I have 3 routes
POST /api/v1/items/ controllers.Application.update
POST /api/v1/items/:item_type controllers.Application.update(item_type: String)
POST /api/v1/items/:item_type/:id/ controllers.Application.update(item_type: String, id: Int)
and 3 corresponding actions for them. And one error:
[error] /my_app/conf/routes:3: method update is defined twice
[error] conflicting symbols both originated in file '/home/alex/my_app/target/scala-2.10/src_managed/main/routes_reverseRouting.scala'
[error] POST /api/v1/items/:item_type/:id/ controllers.Application.update(item_type: String, id: Int)
Please notice that should not be any default value for the parameters that is why I need these actions to be separated.
In play methods are called by name. Parameters are omitted. Name of method has to be unique. You can not have the same name for controllers (if you have in two packages)
Please use default parameters:
POST /api/v1/items/ controllers.Application.update(item_type: String = "", id: Int = 0)
POST /api/v1/items/:item_type controllers.Application.update(item_type: String, id Int =0)
POST /api/v1/items/:item_type/:id/ controllers.Application.update(item_type: String, id: Int)

Scala Play passing variable to view not working

This code works fine:
In the controller:
Ok(views.html.payment(message,test,x_card_num,x_exp_date,exp_year,exp_month,x_card_code,x_first_name,x_last_name,x_address,x_city,x_state,x_zip,save_account,product_array,x_amount,products_json,auth_net_customer_profile_id,auth_net_payment_profile_id,customer_id))
In the view:
#(message: String, test: String, x_card_num: String, x_exp_date: String,exp_year: String, exp_month: String, x_card_code: String, x_first_name: String, x_last_name: String, x_address: String, x_city: String, x_state: String, x_zip: String, save_account: String, product_array: Map[String,Map[String,Any]], x_amount: String, products_json: String, auth_net_customer_profile_id: String,auth_net_payment_profile_id: String,customer_id: String)
But when I try to add one more variable to the controller and view like this:
Ok(views.html.payment(message,test,x_card_num,x_exp_date,exp_year,exp_month,x_card_code,x_first_name,x_last_name,x_address,x_city,x_state,x_zip,save_account,product_array,x_amount,products_json,auth_net_customer_profile_id,auth_net_payment_profile_id,customer_id,saved_payments_xml))
#(message: String, test: String, x_card_num: String, x_exp_date: String,exp_year: String, exp_month: String, x_card_code: String, x_first_name: String, x_last_name: String, x_address: String, x_city: String, x_state: String, x_zip: String, save_account: String, product_array: Map[String,Map[String,Any]], x_amount: String, products_json: String, auth_net_customer_profile_id: String,auth_net_payment_profile_id: String,customer_id: String, saved_payments_xml: String)
It gives me this error:
missing parameter type
What am I doing wrong?
There's a limit to the number of parameters you can pass to a template. You've exceeded it when you add another parameter.
It's an undocumented and fairly arbitrary limit which is the result of how the code generation from a template works. It is arguably a bug, but not one that I would fix since nobody needs this many parameters, and having this many makes code much less readable.
Your best resolution here is to refactor, for example by creating some case classes to represent Card and Address in your model, and pass those in instead.