Extract path parameters with akka http routing dsl - scala

Akka HTTP has good support in the routing dsl for extracting path query parameters (those following the ?, concatenated by &), but not for path parameters separated by ; (such as /my/path;JSESSIONID=123)
How is this best accomplished?

Easier than I thought. Could probably remove dependency on scalaz (Lens) and optimize the code a bit, but this will do for now.
Btw, also discovered that path params destroys the ability to match paths with the path directive:
/my/path;JSESSIONID=123 does not match the directive path("my" / "path")
The solution below addresses this by removing the path params from the request context, and instead providing them.
Note to Akka guys: maybe you could incorporate something similar in the framework so the next guy that looks for getting JSESSIONID from path params doesn't have to implement the same?
def pathParams: Directive1[List[String]] = {
val prv = provide(List.empty[String])
def somePathParams(ctxPathLens: Lens[RequestContext, Path]) =
extract(ctx => (Slash ~ Segments).apply(ctxPathLens.get(ctx))).flatMap {
case Matched(_, Tuple1(path)) =>
path.takeRight(1) match {
case last :: Nil => last.split(';').toList match {
case lastHead :: lastTail => provide(lastTail) & mapRequestContext(
ctxPathLens.set(_, Path((path.dropRight(1) :+ lastHead).mkString("/", "/", ""))))
case _ => prv
}
case _ => prv
}
case _ => prv
}
val unmatchedPath = somePathParams(Lens.lensu((ctx, path) =>
ctx.mapUnmatchedPath(_ => path),
_.unmatchedPath))
val requestPath = somePathParams(Lens.lensu((ctx, path) =>
ctx.mapRequest(r => r.withUri(r.uri.withPath(path)))
, _.request.uri.path))
unmatchedPath.tflatMap(_ => Directive.Empty) & requestPath
}
def pathParamsMap: Directive1[Map[String, String]] =
pathParams.map(_.map(_.split('=').toList match {
case key :: Nil => key -> ""
case key :: values => key -> values.mkString("=")
case _ => ???
}).toMap)
def optionalPathParam(name: String): Directive1[Option[String]] =
pathParamsMap.map(_.get(name))
def optionalPathParamSessionId:Directive1[Option[UUID]] =
optionalPathParam(jsessionidKey).map(_.flatMap(j => Try(UUID.fromString(j)).toOption))

Related

Optimization of foldLeft

I'm using the following code, and I'm looking for some ideas to make some optimizations.
analyzePayload:
Input: payload which is JsObject and list of rules, each rule has several conditions.
Output: MyReport of all the rules which succeed, notApplicable or failed on this specific payload.
The size of the list can be pretty big, also each Rule has a big amount of conditions.
I am looking for some ideas on how to optimize that code, maybe with a lazy collection? view? stream? tailrec? and why - Thanks!
Also, note that I have anaylzeMode which can run only until one rule succeeds for ex.
def analyzePayload(payload: JsObject, rules: List[Rule]): MyReport = {
val analyzeMode = appConfig.analyzeMode
val (succeed, notApplicable, failed) = rules.foldLeft((List[Rule](), List[Rule](), List[Rule]())) { case ( seed # (succeedRules,notApplicableRules,failedRules), currRule) =>
// Evaluate Single Rule
def step(): (List[Rule], List[Rule], List[Rule]) = evalService.eval(currRule, payload) match {
// If the result is succeed
case EvalResult(true, _, _) => (currRule :: succeedRules, notApplicableRules, failedRules)
// If the result is notApplicable
case EvalResult(_, missing # _ :: _, _) => (succeedRules, currRule :: notApplicableRules, failedRules
)
// If the result is unmatched
case EvalResult(_, _, unmatched # _ :: _) => (succeedRules, notApplicableRules, currRule :: failedRules)
}
analyzeMode match {
case UNTIL_FIRST_SUCCEED => if(succeedRules.isEmpty) step() else seed
case UNTIL_FIRST_NOT_APPLICABLE => if(notApplicableRules.isEmpty) step() else seed
case UNTIL_FIRST_FAILED => if(failedRules.isEmpty) step() else seed
case DEFAULT => step()
case _ => throw new IllegalArgumentException(s"Unknown mode = ${analyzeMode}")
}
}
MyReport(succeed.reverse, notApplicable.reverse, failed.reverse)
}
First Edit:
Changed the code to use tailrec from #Tim Advise, any other suggestions? or some suggestions to make the code a little prettier?
Also, i wanted to ask if there any difference to use view before the foldLeft on the previous implementation.
Also maybe use other collection such as ListBuffer or Vector
def analyzePayload(payload: JsObject, actionRules: List[ActionRule]): MyReport = {
val analyzeMode = appConfig.analyzeMode
def isCompleted(succeed: List[Rule], notApplicable: List[Rule], failed: List[Rule]) = ((succeed, notApplicable, failed), analyzeMode) match {
case (( _ :: _, _, _), UNTIL_FIRST_SUCCEED) | (( _,_ :: _, _), UNTIL_FIRST_NOT_APPLICABLE) | (( _, _, _ :: _), UNTIL_FIRST_FAILED) => true
case (_, DEFAULT) => false
case _ => throw new IllegalArgumentException(s"Unknown mode on analyzePayload with mode = ${analyzeMode}")
}
#tailrec
def _analyzePayload(actionRules: List[ActionRule])(succeed: List[Rule], notApplicable: List[Rule], failed: List[Rule]): (List[Rule], List[Rule] ,List[Rule]) = actionRules match {
case Nil | _ if isCompleted(succeed, notApplicable, failed) => (succeed, notApplicable, failed)
case actionRule :: tail => actionRuleService.eval(actionRule, payload) match {
// If the result is succeed
case EvalResult(true, _, _) => _analyzePayload(tail)(actionRule :: succeed, notApplicable, failed)
// If the result is notApplicable
case EvalResult(_, missing # _ :: _, _) => _analyzePayload(tail)(succeed, actionRule :: notApplicable, failed)
// If the result is unmatched
case EvalResult(_, _, unmatched # _ :: _) => _analyzePayload(tail)(succeed, notApplicable, actionRule :: failed)
}
}
val res = _analyzePayload(actionRules)(Nil,Nil,Nil)
MyReport(res._1, res._2, res._3)
}
Edit 2: (Questions)
If there result will be forwarded to the Client - There no meaning for do it as view? since all the data will be evaluated right?
Maybe should I use ParSeq instead? or this will be just slower since the operation of the evalService.eval(...) is not a heavy operation?
Two obvious optimisations:
Use a tail-recursive function rater than foldLeft so that the compiler can generate an optimised loop and terminate as soon as the appropriate rule is found.
Since analyzeMode is constant, take the match outside the foldLeft. Either have separate code paths for each mode, or use analyzeMode to select a function that is used inside the loop to check for termination.
The code is rather fine, the main thing to revisit would be to make evalService.eval evaluate multiple rules in a single traversal of the json object, assuming the size of the json is not negligible

Pattern matching against URL with question mark in Scala

I am trying to extract few values from the URL consisting of question mark.
However, the below code doesn't work. Would you please help me in figuring out what went wrong?
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)?book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
println("my url:"+url)
url.getPath match {
case LibraryPattern(libId, bookId) =>
println(libId)
println(bookId)
case _ =>
}
}
As few answer already pointed how to fix code example, I want to suggest another solution. Parsing URL with regex may be inefficient in terms of future readability, type safety and flexability of your codebase.
I want to suggest using scala-uri library or something similar.
With this library one can do url parsing as simple as:
import io.lemonlabs.uri.Url
val url = Url.parse("https://bookscollection.com/library/mylib?book=abc")
val lastPathPart = url.path.parts.last
// println(lastPathPart)
// res: String = "mylib"
val bookParam: Option[String] = url.query.param("book")
// println(bookParam)
// res: Option[String] = Some("abc")
The URL object has already parsed the URL for you. getPath returns everything before the ?, use getQuery to obtain the part after the ?:
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)".r
val BookPattern = "book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
url.getPath match {
case LibraryPattern(libId) =>
url.getQuery match {
case BookPattern(bookId) =>
println(libId)
println(bookId)
}
}
}
? is a special character in Regex (it essentially makes the previous character/group optional). You'll need to escape it.
EDIT: url.getPath only returns /library/mylib, so you shouldn't use this if you want your Regex to match.
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)\\?book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
println("my url:"+url)
url.toString match {
case LibraryPattern(libId, bookId) =>
println(libId)
println(bookId)
case _ =>
}
}

How to match path segments in URI with Akka-http low-level API

I'm trying to implement a REST API using the akka-http low-level API. I need to match a request for paths containing resource ids, for instance, "/users/12", where the 12 is the id of the user.
I'm looking for something along these lines:
case HttpRequest(GET, Uri.Path("/users/$asInt(id)"), _, _, _) =>
// id available as a variable
The "$asInt(id)" is a made up syntax, I'm using it just to describe what I want to do.
I can easily find examples of how to do this with the high level API using routes and directives, but I can't find anything with the low-level API. Is this possible with the low-level API?
I found a post in the Akka user list saying that the low-level API does not support this type of path segment extraction:
https://groups.google.com/forum/#!topic/akka-user/ucXP7rjUbyE/discussion
The alternatives are either to use the routing API or to parse the path string ourselves.
My team has found a nice solution to this:
/** matches to "/{head}/{tail}" uri path, where tail is another path */
object / {
def unapply(path: Path): Option[(String, Path)] = path match {
case Slash(Segment(element, tail)) => Some(element -> tail)
case _ => None
}
}
/** matches to last element of the path ("/{last}") */
object /! {
def unapply(path: Path): Option[String] = path match {
case Slash(Segment(element, Empty)) => Some(element)
case _ => None
}
}
An example usage (where the expect path is "/event/${eventType}")
val requestHandler: HttpRequest => Future[String] = {
case HttpRequest(POST, uri, _, entity, _) =>
uri.path match {
case /("event", /!(eventType)) =>
case _ =>
}
case _ =>
}
More complex scenarios can be handled by chaining calls to /, ending with a call to /!.

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 match a string on a prefix and get the rest?

I can write the code like this:
str match {
case s if s.startsWith("!!!") => s.stripPrefix("!!!")
case _ =>
}
But I want to know is there any better solutions. For example:
str match {
case "!!!" + rest => rest
case _ =>
}
val r = """^!!!(.*)""".r
val r(suffix) = "!!!rest of string"
So suffix will be populated with rest of string, or a scala.MatchError gets thrown.
A different variant would be:
val r = """^(!!!){0,1}(.*)""".r
val r(prefix,suffix) = ...
And prefix will either match the !!! or be null. e.g.
(prefix, suffix) match {
case(null, s) => "No prefix"
case _ => "Prefix"
}
The above is a little more complex than you might need, but it's worth looking at the power of Scala's regexp integration.
Starting Scala 2.13, it's now possible to pattern match a String by unapplying a string interpolator:
"!!!hello" match {
case s"!!!$rest" => rest
case _ => "oups"
}
// "hello"
If it's the sort of thing you do often, it's probably worth creating an extractor
object BangBangBangString{
def unapply(str:String):Option[String]= {
str match {
case s if s.startsWith("!!!") => Some(s.stripPrefix("!!!"))
case _ => None
}
}
}
Then you can use the extractor as follows
str match{
case BangBangBangString(rest) => println(rest)
case _ => println("Doesn't start with !!!")
}
or even
for(BangBangBangString(rest)<-myStringList){
println("rest")
}
Good question !
Even i was trying a lot to find out the answer.
Here is a good link where I found the answer
object _04MatchExpression_PatternGuards {
def main(args: Array[String]): Unit = {
val url: String = "Jan";
val monthType = url match {
case url if url.endsWith(".org") => "Educational Websites";
case url if url.endsWith(".com") => "Commercial Websites";
case url if url.endsWith(".co.in") => "Indian Websites"
case _ => "Unknow Input";
}
}
}