Lift: Menu with multiple params [Menu.params] - scala

How to build an url with two or many params?
i've a case class:
case class PageDetail(index: String, id: String)
i'm trying to do a menu val:
val menu = Menu.params[(String,String)]( "pageDetail", "Page Detail",
ids => { case Full(index) :: Full(id) :: Nil => Full((index, id))},
pi => { case (index, id) => index :: id :: Nil }) / "admin" / "detail"
i would like to obtain a link as .../admin/detail/indexxxxxxx/idddddddddd where indexxxxxxx and idddddddddd are my params.
as is doesn't work. Error in compile time. How can i do?
Thanks

Most likely, the issue is in your extractor pattern. When you are matching on your list here:
case Full(index) :: Full(id) :: Nil => Full((index, id))
The parameters are always going to be defined, so the Full is not possible. You can use functions, such as AsInt to require the parameter to be an Int, or else it will look for a String. You'd most likely want to start with the following (Or some variation on that):
case index :: id :: Nil => Full((index, id))
If you are using Empty to mean the parameter is optional, then you would simply add a second case statement after it with the parameter omitted.
Also, you probably need to add / ** to the end of you / "admin" / "detail" mapping so it knows to grab the parameters from there.
So, the code should look something like this:
val menu = Menu.params[(String,String)]( "pageDetail", "Page Detail",
{
case index :: id :: Nil => Full((index, id))
}, {
case (index, id) => index :: id :: Nil
}
) / "admin" / "detail" / **

Related

Dynamic Stable identifiers in Scala Pattern Match

Using Scala, is there any way to dynamically construct a list patterns to be pattern matched against?
For example, suppose I'm using stable identifiers to parse a list of Strings, like this:
def matchingAndDispatch(xs: List[String])= {
case `namespace` :: value :: `elementTerminator` :: rest => {
// Do Something...
}
case `openBracket` :: rest => {
// Do Something Else...
}
case `closeBracket` :: `elementTerminator` :: rest => {
// Or perhaps something else...
}
}
Now, suppose there are going to be a lot of case clauses and I wanted the ability to store them in a collection of some sort that could be changed at runtime - not necessarily the patterns themselves, but the collection of patterns could be changed. I've made up the imaginary class MatchClause in the code below to explain more or less what I have in mind - basically traverse a collection of pattern(i.e. Match Clauses) and match one at a time:
def matchingAndDispatch(xs: List[String], matchingClauses:List[MatchClause])= {
if(!matchingClauses.empty){
case matchingClauses.head => {
// Do Something...
}
case _ => matchingAndDispatch(xs, matchingClause.tail)
}
}else throw new Error("no match")
Is there anything in the Scala API that would serve this purpose? I haven't found anything. Or perhaps I'm going about this the wrong way?
val `namespace` = "namespace"
val `elementTerminator` = "elementTerminator"
val `openBracket` = "openBracket"
val `closeBracket` = "closeBracket"
// list of partial functions from list of strings to string:
val patterns = List[PartialFunction[List[String], String]](
{ case `namespace` :: value :: `elementTerminator` :: rest => "case1" },
{ case `openBracket` :: rest => "case2" },
{ case `closeBracket` :: `elementTerminator` :: rest => "case3" })
def matchingAndDispatch(xs: List[String], patterns: List[PartialFunction[List[String], String]]): String = {
patterns.find(_.isDefinedAt(xs)).map(_(xs)).getOrElse("unknown")
}
Test:
matchingAndDispatch(List("namespace", "somevalue", "elementTerminator"), patterns)
> case1
matchingAndDispatch(List("namespace", "somevalue", "elementTerminator", "more"), patterns)
> case1
matchingAndDispatch(List("namespace", "somevalue", "not_terminator", "more"), patterns)
> unknown
You can declare a stable identifier within a local scope. That is, you can write
val hd = matchingClauses.head
xs match {
case `hd` => ???
}

How to get the correct parameter validation error

I have a simple route, where the parameters should be extracted into case classes:
val myRoute: Route =
get {
path("resource") {
parameters('foo, 'x.as[Int]).as(FooParams) { params =>
...
} ~
parameters('bar, 'x.as[Int]).as(BarParams) { params =>
...
}
}
}
case class FooParams(foo: String, x: Int) {
require(x > 1 && x < 10, "x for foos must be between 2 and 9")
}
case class BarParams(bar: String, x: Int) {
require(x > 10 && x < 20, "x for bars must be between 11 and 19")
}
The case classes should validate the input, so invalid input would be rejected with a 400.
The rejection happens, but with a 404 and the error message is misleading
I expect it to be x for foos must be between 2 and 9 for .../resource?foo=a&x=0 but it is Request is missing required query parameter 'bar'
Same for bars, while I expect .../resource?bar=a&x=0 to result in a 400 with x for bars must be between 11 and 19, it responds with a 404 with Request is missing required query parameter 'foo'.
What am I misunderstanding here and how to fix it?
akka-http 2.0.3
EDIT
4lex1v's solution works for me. What bothers me a bit is that I am deliberately abandoning the help the framework offers me: I have to handle the case where both foo and bar are missing 'manually'. Same goes for the rejections on x ranges. OTOH, the code is much more explicit, including the handling for the case where both foo and bar are given and the MissingQueryParamRejection can be customized when both are missing:
val myRoute2: Route =
(get & path("resource")) {
parameters('foo ?, 'bar ?, 'x.as[Int]) {
case (Some(foo), None, x) if x > 1 && x < 10 => {
val params = FooParams(foo, x)
...
}
case (Some(foo), None, x) => reject(MalformedQueryParamRejection("x", s"x for foos must be between 2 and 10 but was $x"))
case (None, Some(bar), x) if x > 10 && x < 20 => {
val params = BarParams(bar, x)
...
}
case (None, Some(bar), x) => reject(MalformedQueryParamRejection("x", s"x for bars must be between 11 and 19 but was $x"))
case (Some(foo), Some(bar), x) => reject(MalformedQueryParamRejection("bar", "expecting either foo or bar, received both"))
case (None, None, x) => reject(MissingQueryParamRejection("foo or bar"))
}
}
I think the main part of the issue you are seeing comes from how your route is defined. By defining both of those possible parameter sets under the path "resource", then when it misses on the foo param you end up with a MissingParemeterRejection at the head of the list of rejections. A ValidationRejection ends up in there too, but the default rejection handler must prefer the MissingParameterRejection when deciding whats status code and message to convey to the caller. If you simply redefined your routes like so:
val myRoute: Route =
get {
path("resource") {
parameters('foo, 'x.as[Int]).as(FooParams) { params =>
...
} ~
}
path("resource2"){
parameters('bar, 'x.as[Int]).as(BarParams) { params =>
...
}
}
}
Then everything works as expected. In this case, it doesn't even attempt to evaluate the params until it has accepted the root path. And with each root path having a different param set, there is no chance of getting that unnecessary missing param exception at the head of the list.
Now if that's not an acceptable alternative, then you can wrap that route with something like mapRejections to remove the unnecessary missing param rejection if it contains a validation rejection. Something like this:
val validationWins = mapRejections{ rej =>
val mapped = rej.filter(_.isInstanceOf[ValidationRejection])
if (mapped.isEmpty) rej else mapped
}
val myRoute =
get {
path("resource") {
validationWins{
parameters('foo, 'x.as[Int]).as(FooParams) { params =>
complete(StatusCodes.OK)
} ~
parameters('bar, 'x.as[Int]).as(BarParams) { params =>
complete(StatusCodes.OK)
}
}
}
Ideally, I prefer to use cancelRejections in my route tree to remove things that don't matter going forward in the tree, but there wasn't a clean place to do that, so I used mapRejections instead.
I wouldn't do it with two distinct parameters directives, instead i would advice to use one and make your parameters as option, i.e parameters('foo?, 'bar?, x.as[Int]). This directive would extract the data you need, which you can later match on and convert the the case you need, something like this:
(get & path("...")) {
parameters('foo?, 'bar?, x.as[Int]) {
case (None, Some(bar), x) => BarParams(bar, x)
case (Some(foo), None, x) => FooParams(foo, x)
/**
* Here comes you Rejection (you can make your own)
*/
case _ => reject(MalfromedParametersRejection)
}
}
Another thing i'd consider a bad practice using require in constructor, given solution allows you to use guards to handle the case you've described:
parameters('foo?, 'bar?, x.as[Int]) {
case (None, Some(bar), x) if x > 1 && x < 10 => BarParams(bar, x)
//...
}

Json4s custom serializer with unordered fields

In the example given on the json4s readme https://github.com/json4s/json4s#serializing-non-supported-types the match only works if the fields are order {"start":0,"end":0}. If the start and end fields are swapped then the match doesn't work. Is there anyway to write the below case match such that the JSON field ordering doesn't matter?
case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil)
I haven't used this library, so I am not sure if this is the right approach: (I came up with this after spending a couple of minutes looking at the docs)
class IntervalSerializer extends CustomSerializer[Interval](format => (
{
case x: JObject =>
x.obj.sortBy { case (k,_) => k } match {
case JField("end", JInt(e)) :: JField("start", JInt(s)) :: Nil =>
new Interval(start = s.longValue(), end = e.longValue())
}
},
{
case x: Interval =>
JObject(JField("start", JInt(BigInt(x.startTime))) ::
JField("end", JInt(BigInt(x.endTime))) :: Nil)
}
))
The idea is to sort the fields alphabetically, and then create the Interval class.
I had a different but related issue that made me discover the the "extract" function in json4s. It solves the ordering issue.
case x: JObject =>
Interval((x \ "start").extract[Int],(x \ "end").extract[Int])
If you need a more involved example, you can check this github ticket.

How to take zero or more params with Lift Web Framework

How can I write a snippet that will take either zero or more params and make them available to me in the render method?
I can't seem to find a complete example anywhere, basically what I want is to be able to take
localhost:8080
localhost:8080/
localhost:8080/somevalue
and be able to access a case object that is either Full(someValue) or Empty.
Something like this basically, from https://www.assembla.com/spaces/liftweb/wiki/Location_Parameters
menu = Menu.params[(Product,ProductReview)](
"ProdInfo",
"Product Information",
ids => {
case prodId :: revId :: Nil =>
(findProductById(prodId), findReviewById(revId)) match {
case (Full(product), Full(review)) => Full((product, review))
case _ => Empty
}
case _ =>
Empty
}
productandreview => {
case (product, review) =>
getIdForProduct(product) :: getIdForReview(review) :: Nil
}
) / "products" >> If(() => S.loggedIn_?, () => RedirectResponse("/login"))
I want to do an even simpler version of that and would appreciate any help. I have been working with the "shop with me" and "pocketchange" example projects, but haven't figured out how to connect all the dots yet.
If you want quicker responses you should probably look into the Lift's official support channel, and that is their mailing list. https://groups.google.com/forum/#!forum/liftweb
To handle the URL you have several options. Ones that I know of are:
just get the URL and handle it manually, via S.uri / s.request.*.uri
use REST. You can specify an unknown or optional path there. https://www.assembla.com/wiki/show/liftweb/REST_Web_Services
One of the easiest solutions would be to have a wrapper, like:
case class ProductTupleMode(items:Box[(Product,ProductReview)] = Empty)
Then you can would setup your site rule to be something like below:
menu = Menu.params[ProductTupleMode](
"ProdInfo",
"Product Information",
ids => {
case prodId :: revId :: Nil =>
(findProductById(prodId), findReviewById(revId)) match {
case (Full(product), Full(review)) =>
Full(ProductTupleMode(Box !! (product, review)))
case _ => Full(ProductTupleMode())
}
case _ =>
Full(ProductTupleMode())
},
productandreview => {
case ProductTupleMode(Full((product, review))) =>
getIdForProduct(product) :: getIdForReview(review) :: Nil
}
) / "products" / ** >> If(() => S.loggedIn_?, () => RedirectResponse("/login"))
And in your snippet snippet you could then use either value:
class MySnippet(m:ProductTupleMode) {
def render = m match {
case Full(m.items) => ...
case _ => ...
}
}
I think that would be the cleanest and easiest to follow, but if you really need to have the signature Box[(Product, ProductReview)] in your snippet then you could also return Box[Box[(Product, Product)]] like this:
menu = Menu.params[Box[(Product,ProductReview)]](
"ProdInfo",
"Product Information",
ids => {
case prodId :: revId :: Nil =>
(findProductById(prodId), findReviewById(revId)) match {
case (Full(product), Full(review)) => Full(Full((product, review)))
case _ => Full(Empty)
}
case _ =>
Full(Empty)
},
productandreview => {
case Full((product, review)) =>
getIdForProduct(product) :: getIdForReview(review) :: Nil
}
) / "products" / ** >> If(() => S.loggedIn_?, () => RedirectResponse("/login"))

How to create a List of Wildcard elements Scala

I'm trying to write a function that returns a list (for querying purposes) that has some wildcard elements:
def createPattern(query: List[(String,String)]) = {
val l = List[(_,_,_,_,_,_,_)]
var iter = query
while(iter != null) {
val x = iter.head._1 match {
case "userId" => 0
case "userName" => 1
case "email" => 2
case "userPassword" => 3
case "creationDate" => 4
case "lastLoginDate" => 5
case "removed" => 6
}
l(x) = iter.head._2
iter = iter.tail
}
l
}
So, the user enters some query terms as a list. The function parses through these terms and inserts them into val l. The fields that the user doesn't specify are entered as wildcards.
Val l is causing me troubles. Am I going the right route or are there better ways to do this?
Thanks!
Gosh, where to start. I'd begin by getting an IDE (IntelliJ / Eclipse) which will tell you when you're writing nonsense and why.
Read up on how List works. It's an immutable linked list so your attempts to update by index are very misguided.
Don't use tuples - use case classes.
You shouldn't ever need to use null and I guess here you mean Nil.
Don't use var and while - use for-expression, or the relevant higher-order functions foreach, map etc.
Your code doesn't make much sense as it is, but it seems you're trying to return a 7-element list with the second element of each tuple in the input list mapped via a lookup to position in the output list.
To improve it... don't do that. What you're doing (as programmers have done since arrays were invented) is to use the index as a crude proxy for a Map from Int to whatever. What you want is an actual Map. I don't know what you want to do with it, but wouldn't it be nicer if it were from these key strings themselves, rather than by a number? If so, you can simplify your whole method to
def createPattern(query: List[(String,String)]) = query.toMap
at which point you should realise you probably don't need the method at all, since you can just use toMap at the call site.
If you insist on using an Int index, you could write
def createPattern(query: List[(String,String)]) = {
def intVal(x: String) = x match {
case "userId" => 0
case "userName" => 1
case "email" => 2
case "userPassword" => 3
case "creationDate" => 4
case "lastLoginDate" => 5
case "removed" => 6
}
val tuples = for ((key, value) <- query) yield (intVal(key), value)
tuples.toMap
}
Not sure what you want to do with the resulting list, but you can't create a List of wildcards like that.
What do you want to do with the resulting list, and what type should it be?
Here's how you might build something if you wanted the result to be a List[String], and if you wanted wildcards to be "*":
def createPattern(query:List[(String,String)]) = {
val wildcard = "*"
def orElseWildcard(key:String) = query.find(_._1 == key).getOrElse("",wildcard)._2
orElseWildcard("userID") ::
orElseWildcard("userName") ::
orElseWildcard("email") ::
orElseWildcard("userPassword") ::
orElseWildcard("creationDate") ::
orElseWildcard("lastLoginDate") ::
orElseWildcard("removed") ::
Nil
}
You're not using List, Tuple, iterator, or wild-cards correctly.
I'd take a different approach - maybe something like this:
case class Pattern ( valueMap:Map[String,String] ) {
def this( valueList:List[(String,String)] ) = this( valueList.toMap )
val Seq(
userId,userName,email,userPassword,creationDate,
lastLoginDate,removed
):Seq[Option[String]] = Seq( "userId", "userName",
"email", "userPassword", "creationDate", "lastLoginDate",
"removed" ).map( valueMap.get(_) )
}
Then you can do something like this:
scala> val pattern = new Pattern( List( "userId" -> "Fred" ) )
pattern: Pattern = Pattern(Map(userId -> Fred))
scala> pattern.email
res2: Option[String] = None
scala> pattern.userId
res3: Option[String] = Some(Fred)
, or just use the map directly.