What is the proper way to pass query string parameters to bs-fetch?
Currently, I have:
Fetch.fetch("https://example.com/api?param1=value1¶m2=value2")
Obviously, this is not sustainable for larger parameter lists.
Is there a better way to do this?
re:fetch supports query params by way of either
request("https://example.com/api",
~queryParams=[
("param1", "value1"),
("param2", "value2")
])
|> fetch;
or
request("https://example.com/api")
|> Request.param("param1", "value1")
|> Request.param("param2", "value2")
|> fetch;
Beware that the library is experimental though. Alternatively, you could just swipe the query builder code, which has been battle-tested at least a little bit (there's a subtle bug in #monssef's implementation when there's an empty list, and it also doesn't do proper encoding):
[#bs.val] external encodeURIComponent : string => string = "";
let _buildUrl = (url, params) => {
let encodeParam = ((key, value)) =>
encodeURIComponent(key) ++ "=" ++ encodeURIComponent(value);
let params =
params |> List.map(encodeParam)
|> String.joinWith("&");
switch params {
| "" => url
| _ => {j|$url?$params|j}
};
};
i don't think there's something builtin for that.
just make your own query builder function, something like this
let payload = Js.Dict.empty();
Js.Dict.set(payload, "email", Js.Json.string("email#email.co"));
Js.Dict.set(payload, "password", Js.Json.string("secret"));
let query =
Js.Dict.keys(payload)
|> Array.fold_left(
(query, key) =>
switch (Js.Dict.get(payload, key)) {
| Some(value) =>
query ++ key ++ "=" ++ Js.Json.stringify(value) ++ "&"
| _ => query
},
"?"
);
here's a link to the playground.
Related
I want to match a string with list of regex within a Map[String, List[Regex]] and return the key[String] as String in case there is a match.
e.g:
//Map[String, List[Regex]]
Map(m3 -> List(([^ ]*)(rule3)([^ ]*)), m1 -> List(([^ ]*)(rule1)([^ ]*)), m4 -> List(([^ ]*)(rule5)([^ ]*)), m2 -> List(([^ ]*)(rule2)([^ ]*)))
if the string is "***rule3****" it should return me the key "m3", similarly if the string is "****rule5****" it should return key "m4".
How do i implement this?
something that i tried which is not working
rulesMap.mapValues (y => y.par.foreach (x => x.findFirstMatchIn("description"))).keys.toString()
For Scala 2.13.x
rulesMap
.filter({ case (_, regexList) => regexList.exists(regex => regex.matches("yourString")) })
.keys
For Scala 2.12.x
rulesMap
.filter({ case (_, regexList) => regexList.exists(regex => regex.findFirstIn("yourString").isDefined) })
.keys
collect is the best way of both filtering and mapping a collection because it only does a single pass over the data.
def findKeys(s: String) =
rulesMap.collect {
case (key, exps) if exps.exists(_.findFirstIn(s).nonEmpty) => key
}
I have a simple Scops parser that looks like
val parser: scopt.OptionParser[Config] = new scopt.OptionParser[Config]("my-app") {
head("scopt", "3.x")
(...)
opt[String]('q', "query")
.text("The query.")
.action { (value, conf) => conf.copy(
query = value
)}
.optional
}
(...)
parser.parse(args, Config()) match {
case Some(config) => drive(config)
(...)
In my driver function I want to initialize a parameter with what the user provided via the argument or some default value otherwise.
I could do something like
var myQuery = _
if config.query != "" myQuery = config.query
else myQuery = config.query
But, (i) I doubt that testing against the empty string is the right way to check if the user provided an optional argument or not and (ii) that doesn't look like a very functional to write this in Scala.
Question: Is there a nice functional way to do this? I considered pattern matching like below but the Scopt arguments do not seem to be returned as Option[String]
val myQuery: String = config.query match {
case Some(q) => q
case None => "This is the default query"
}
Just make Config#query an Option[String] (with None as default) and change the query = value line to query = Some(value).
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))
Example context:
An HTTP Response with a body as follows:
key1=val1&key2=val2&key3=val3.
The names of the keys are always known.
Currently the extraction is done with regex:
val params = response split ("""&""") map { _.split("""=""") } map { el => { el(0) -> el(1) } } toMap;
Is there a simpler way of pattern matching the response for specific params?
I think using split is probably going to be the fastest/simplest solution here. You're not doing any advanced parsing, so using parser combinators or regex capture groups seems a little overkill.
However, when you have complex expressions involving multiple calls to map, filter, etc., it's usually an indicator that you can clean things up with a for-comprehension:
val response = "key1=val1&key2=val2&key3=val3"
val params = (for { x <- response split ("&")
Array(k, v) = x split ("=") }
yield k->v).toMap
You can use parser combinators here for most flexibility and robustness (i.e., handle failed parsing):
object Parser extends RegexParsers with App {
def lit: Parser[String] = "[^=&]+".r
def pair: Parser[(String, String)] = lit ~ "=" ~ lit ^^ {
case key ~ "=" ~ value => key -> value
}
def parse: Parser[Seq[(String, String)]] = repsep(pair, "&")
val response = "key1=val1&key2=val2&key3=val3"
val params = parse(new CharSequenceReader(response)).get.toMap
println(params)
}
You can use regexp as a matcher like this:
val r = "([^=]+)=([^=]+)".r
def toKv(s:String) = s match {
case r(k,v) => (k,v)
case _ => throw InvalidFormatException
}
So, for your case it would look like:
response split ("&") map (toKv)
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.