How do I make case insensitive parameters in Akka http? - scala

For example I have a directive:
val sourceTypeDirective: Directive1[DocumentSource] = {
parameters('docsource.as[String]) flatMap {
case docSource if sourceAcceptedType(docSource) => provide(sourceValueOf(docSource))
case _ => reject
}
}
I want to get docsource even if it will be written as DOCSOURCE or DocSource, how can I make that?

No Direct Solution
I don't think this is possible with the parameters Directive directly. The 'docsource Symbol is being converted into a NameReceptacle via an implicit conversion method in ParameterDirectives. There is only 1 String member variable in NameReceptacle: name. This prevents any sort of regular expression matching or converting the Symbol into multiple Strings to match all possible capitalization patterns...
Indirect Solution
The parameterMap Directive can be used to get a Map[String, String] where you can then operate on the keys:
val sourceTypeDirective : Directive1[DocumentSource] =
parameterMap {
(_ : Map[String, String])
.view
.map(tup => (tup._1.toLowerCase, tup._2))
.toMap
.get("docsource")
.filter(sourceAcceptedType)
.map(docSource => provide(sourceValueOf(docSource)))
.getOrElse(reject)
}
Warning
The query string key "field" is distinct from the key "FIELD". Those are technically 2 different keys and should correspond to 2 different values. But any solution to the question would result in a key collision for those two fields.

Related

How to use result of scala's List::groupBy?

I have this code. PartnerReader.fetchParters() returns a List[Partner]. Partners have a country attribute that is a String, and a dates attribute that is a list of Calendar objects. I group all Partners by their country. I expect partnersByCountry is a Map[String, List[Partner]].
I then want to associate countries with all the dates from all their Partners. getAllDatesForPartners() returns a List[Calendar] resulting (hopefully) in a Map[String, List[Calendar]]. I attempt to do this with the assignment to allDatesForCountry, but this fails on the call to map with the error Cannot resolve overloaded method 'map'.
Why does this code not work and what's the correct way to do the transformation?
val partnersByCountry = PartnerReader.fetchPartners()
.groupBy(_.country)
val allDatesForCountry = partnersByCountry
.map((country: String, partners: List[Partner]) => {
country -> getAllDatesForPartners(partners)
})
def getAllDatesForPartners(partners: List[Partner]): List[Calendar] = ???
When you .map() over a Map[?,?] you get a sequence of tuples. To break each tuple into its constituent parts you need pattern matching.
.map{case (country: String, partners: List[Partner]) =>
country -> getAllDatesForPartners(partners)
}
Which is the long (documented) way to write...
.map(tup => (tup._1, getAllDatesForPartners(tup._2)))

scala map get the value by key with key case insensitive

I have a map of Map[String, Info], it contains keys which can be either uppercase or lowercase, like this:
person1: PersonInfo1
person2: PersonInfo2
PERSON1: PersonInfo1
i want to get the value for key 'person1', if nothing found I'll try with key of 'PERSON1', I tried this code:
val info = map.get(key) match {
case Some(personInfo) => personInfo
case None =>
map.get(key.toUpperCase()) match {
case Some(personInfo) => personInfo
case None => None
}
}
but this return info as type of Product with Serializable, how can I have info returned as type of PersonInfo? Is there a way in Scala that allow me to get value from map by key and ignore cases of the key?
There are comparators for sorted maps which allow getting from the map case insensitively. Example: https://scastie.scala-lang.org/PfHTh16CROag7PNrknx1sQ
val map = scala.collection.immutable.SortedMap("key1" -> 45, "Key2" -> 43, "KEY3" -> 42)(scala.math.Ordering.comparatorToOrdering(String.CASE_INSENSITIVE_ORDER))
map.get("key1") // Some(45)
map.get("key2") // Some(43)
map.get("key3") // Some(42)
map.get("key4") // None
Your actual problem can be fixed if you return Options on all cases, for example:
val info = map.get(key) match {
case somePi#Some(personInfo) => somePi
case None => map.get(key.toUpperCase()) match {
case Some(personInfo) => Some(personInfo)
case None => None
}
}
Note the somePi# => somePi parts for referring the expression or the Some(personInfo).
Probably worth explaining why you got this error message. I assume personInfo is a case class which implements Product and Serializable, just like None. The common type for them is Product with Serializable.
You could chain gets with orElse. I would create an extension method for that:
implicit class CaseInsensitiveGetMap[V] (m: Map[String,V]) {
def iget (key: String): Option[V] = m.get(key)
.orElse(m.get(key.toUpperCase())) //you can add more orElse in chain
}
Then you can just use it like:
map.iget("person2")
The reason you're getting Product with Serializable is because your code is trying to return either a String (if key is good) or an Option (i.e. None if key not found). Those two types are not compatible. You should decide if you want String (maybe an empty string if key not found) or Option (i.e. Some[String] or None).
See if this works for you. It returns an Option[String].
map.get(key).fold(pm.get(key.toUpperCase))(Some(_))
The 1st get() returns an Option. The fold()() unwraps the Option and either tries a 2nd get() with upper case key value, or, if the 1st get returned a value, the value is re-wrapped in an Option so that the types match up.
If, on the other hand, you want to return a String instead, you might do this.
map.getOrElse(key, pm.getOrElse(key.toUpperCase, ""))
You can do a find instead of a get but you may want to consider performance when doing this.
map.find(k => k._1.equalsIgnoreCase(key)) match {
case Some =>
case None =>
}

Handle Scala Option idiomatically

What is the more idiomatic way to handle an Option, map / getOrElse, or match?
val x = option map {
value => Math.cos(value) + Math.sin(value)
} getOrElse {
.5
}
or
val x = option match {
case Some(value) => Math.cos(value) + Math.sin(value)
case None => .5
}
You could always just look at the Scaladoc for Option:
The most idiomatic way to use an scala.Option instance is to treat it as a collection or monad and use map,flatMap, filter, or foreach:
val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")
And a bit later:
A less-idiomatic way to use scala.Option values is via pattern matching:
val nameMaybe = request getParameter "name"
nameMaybe match {
case Some(name) =>
println(name.trim.toUppercase)
case None =>
println("No name value")
}
Use fold for this kind of map-or-else-default thing:
val x = option.fold(0.5){ value => Math.cos(value) + Math.sin(value) }
Obviously both are valid and I don't think one is more idiomatic than the other. That being said, using map uses the fact the Option is a Monad. This can be particularly advantageous when combining two Options. Say you have two Option[Int] that you would like to add. In this case instead of doing multiple matches it is much cleaner to use map/flatMap and it's equivalent "for comprehensions". So for your example both are valid... but for other examples using map/flatMap is often much more succinct.
Some(6).flatMap(intValue => Some(5).map(intValue + _))
or
for {
i <- Some(6)
j <- Some(5)
} yield i + j
All of them have different semantics, so in your case none of them.
map applies some function to the value inside Option, if it exists (Some, not None). Basically this is how you safely work with Options, appling function on some null value is dangeroues, cause it can throw NPE, but in case with Option it just returns None.
getOrElse simply returns either it's value or default one (which you provide as an argument). It won't do anything with the value inside the Option, you can just extract it, if you have Some, or return a default one, in case of None.
and match approach i'd say is a combination of two, cause you can apply some computation on the values and extract it from the Option

Convert a List[Task(username, description)] into Map[username,Set[Task]]

(NOTE I'm quit new to Scala and still struggle with most common operations of collection manipulation.)
I would like to convert a List[Task] into a Map. Here's some details:
// assignee may be null
case class Task(assignee: String, description: String)
// might refactor it into:
// case class Task(assignee: Option[String], description: String)
I want a Map where Keys are the assignees and each Value is a Set[Task]. I'm having trouble managing the following two situations:
Map's not being (cough) friendly (cough) with null Keys (I worked around this one using Option[String] for assignee) and
having to distinguish whether a Key already exists in the map (only add value to existing set) vs key already added so the Set value exists
I came up with the following but it looks overly verbose.
def groupByAssignee(tasks : List[Task]) : Map[Option[String], Set[Task]] = {
tasks.foldLeft(Map[Option[String], Set[Task]]())(
(m, t) => {
m.get(t.assignee) match {
case Some(_) => m + ((t.assignee, m.get(t.assignee).get.+(t)))
case _ => m + ((t.assignee, Set(t)))
}
})
}
What's a easier/clearer way to achieve this?
Thanks!
This use case is so common that there is a built-in method for that:
tasks groupBy {_.assignee}
groupBy however will return Map[String,List[Task]] while you want .Map[String, Set[String]]. This should do it:
groupBy {_.assignee} mapValues {_ map {_.description} toSet}
groupBY is null-friendly, but you shouldn't be. Option[String] is much better and more idiomatic.

JSON to XML in Scala and dealing with Option() result

Consider the following from the Scala interpreter:
scala> JSON.parseFull("""{"name":"jack","greeting":"hello world"}""")
res6: Option[Any] = Some(Map(name -> jack, greeting -> hello world))
Why is the Map returned in Some() thing? And how do I work with it?
I want to put the values in an xml template:
<test>
<name>name goes here</name>
<greeting>greeting goes here</greeting>
</test>
What is the Scala way of getting my map out of Some(thing) and getting those values in the xml?
You should probably use something like this:
res6 collect { case x: Map[String, String] => renderXml(x) }
Where:
def renderXml(m: Map[String, String]) =
<test><name>{m.get("name") getOrElse ""}</name></test>
The collect method on Option[A] takes a PartialFunction[A, B] and is a combination of filter (by a predicate) and map (by a function). That is:
opt collect pf
opt filter (a => pf isDefinedAt a) map (a => pf(a))
Are both equivalent. When you have an optional value, you should use map, flatMap, filter, collect etc to transform the option in your program, avoiding extracting the option's contents either via a pattern-match or via the get method. You should never, ever use Option.get - it is the canonical sign that you are doing it wrong. Pattern-matching should be avoided because it represents a fork in your program and hence adds to cyclomatic complexity - the only time you might wish to do this might be for performance
Actually you have the issue that the result of the parseJSON method is an Option[Any] (the reason is that it is an Option, presumably, is that the parsing may not succeed and Option is a more graceful way of handling null than, well, null).
But the issue with my code above is that the case x: Map[String, String] cannot be checked at runtime due to type erasure (i.e. scala can check that the option contains a Map but not that the Map's type parameters are both String. The code will get you an unchecked warning.
An Option is returned because parseFull has different possible return values depending on the input, or it may fail to parse the input at all (giving None). So, aside from an optional Map which associates keys with values, an optional List can be returned as well if the JSON string denoted an array.
Example:
scala> import scala.util.parsing.json.JSON._
import scala.util.parsing.json.JSON._
scala> parseFull("""{"name":"jack"}""")
res4: Option[Any] = Some(Map(name -> jack))
scala> parseFull("""[ 100, 200, 300 ]""")
res6: Option[Any] = Some(List(100.0, 200.0, 300.0))
You might need pattern matching in order to achieve what you want, like so:
scala> parseFull("""{"name":"jack","greeting":"hello world"}""") match {
| case Some(m) => Console println ("Got a map: " + m)
| case _ =>
| }
Got a map: Map(name -> jack, greeting -> hello world)
Now, if you want to generate XML output, you can use the above to iterate over the key/value pairs:
import scala.xml.XML
parseFull("""{"name":"jack","greeting":"hello world"}""") match {
case Some(m: Map[_,_]) =>
<test>
{
m map { case (k,v) =>
XML.loadString("<%s>%s</%s>".format(k,v,k))
}
}
</test>
case _ =>
}
parseFull returns an Option because the string may not be valid JSON (in which case it will return None instead of Some).
The usual way to get the value out of a Some is to pattern match against it like this:
result match {
case Some(map) =>
doSomethingWith(map)
case None =>
handleTheError()
}
If you're certain the input will always be valid and so you don't need to handle the case of invalid input, you can use the get method on the Option, which will throw an exception when called on None.
You have two separate problems.
It's typed as Any.
Your data is inside an Option and a Map.
Let's suppose we have the data:
val x: Option[Any] = Some(Map("name" -> "jack", "greeting" -> "hi"))
and suppose that we want to return the appropriate XML if there is something to return, but not otherwise. Then we can use collect to gather those parts that we know how to deal with:
val y = x collect {
case m: Map[_,_] => m collect {
case (key: String, value: String) => key -> value
}
}
(note how we've taken each entry in the map apart to make sure it maps a string to a string--we wouldn't know how to proceed otherwise. We get:
y: Option[scala.collection.immutable.Map[String,String]] =
Some(Map(name -> jack, greeting -> hi))
Okay, that's better! Now if you know which fields you want in your XML, you can ask for them:
val z = for (m <- y; name <- m.get("name"); greet <- m.get("greeting")) yield {
<test><name>{name}</name><greeting>{greet}</greeting></test>
}
which in this (successful) case produces
z: Option[scala.xml.Elem] =
Some(<test><name>jack</name><greeting>hi</greeting></test>)
and in an unsuccessful case would produce None.
If you instead want to wrap whatever you happen to find in your map in the form <key>value</key>, it's a bit more work because Scala doesn't have a good abstraction for tags:
val z = for (m <- y) yield <test>{ m.map { case (tag, text) => xml.Elem(null, tag, xml.Null, xml.TopScope, xml.Text(text)) }}</test>
which again produces
z: Option[scala.xml.Elem] =
Some(<test><name>jack</name><greeting>hi</greeting></test>)
(You can use get to get the contents of an Option, but it will throw an exception if the Option is empty (i.e. None).)