Karate: Can we use "match each" twice in same line for 2 different assertions? - match

Basically, I want to achieve the below assertions in a single statement
* match each obj[*].id != ''
* match each obj[*].name != ''
-> obj: its a list of JSON objects returned as the response to an API
The below code snippet does not work.
* match each obj[*].id != '' && each obj[*].name != ''
can this be achieved in karate?

I think you are un-necessarily using JsonPath like [*]. Best practice in Karate is to express data as JSON. For example:
* def response = [{ a: 1, b: 'x' }, { a: 2, b: 'y' }]
* match each response == { a: '#number', b: '#string' }
So yes you can achieve what you want:
* match each obj contains { id: "#? _ != ''", name: "#? _ != ''" }

Related

Shorten MQTT topic filtering function

I wrote the following logic function, but I am sure it is possible to write it (way) shorter.
In case you are unfamiliar with MQTT wildcards, you can read up on them here.
self is the topic we are "subscribed" to, containing zero or more wildcards. incoming is the topic we received something on, which must match the self topic either fully, or conforming to the wildcard rules.
All my tests on this function succeed, but I just don't like the lengthiness and "iffyness" of this Scala function.
def filterTopic(incoming: String, self: String): Boolean = {
if (incoming == self || self == "#") {
true
} else if (self.startsWith("#") || (self.contains("#") && !self.endsWith("#")) || self.endsWith("+")) {
false
} else {
var valid = true
val selfSplit = self.split('/')
var j = 0
for (i <- selfSplit.indices) {
if (selfSplit(i) != "+" && selfSplit(i) != "#" && selfSplit(i) != incoming.split('/')(i)) {
valid = false
}
j += 1
}
if (j < selfSplit.length && selfSplit(j) == "#") {
j += 1
}
j == selfSplit.length && valid
}
}
Here's a shot at it assuming that '+' can be at the end and that the topics are otherwise well-structured
def filterTopic(incoming: String, self: String): Boolean = {
// helper function that works on lists of parts of the topics
def go(incParts: List[String], sParts: List[String]): Boolean = (incParts, sParts) match {
// if they're equivalent lists, the topics match
case (is, ss) if is == ss => true
// if sParts is just a single "#", the topics match
case (_, "#" :: Nil) => true
// if sParts starts with '+', just check if the rest match
case (_ :: is, s :: ss) if s == "+" =>
go(is, ss)
// otherwise the first parts have to match, and we check the rest
case (i :: is, s :: ss) if i == s =>
go(is, ss)
// otherwise they don't match
case _ => false
}
// split the topic strings into parts
go(incoming.split('/').toList, self.split('/').toList)
}

Validation that returns more than 1 error

I'm doing a few validations using an older Scala version (2.0) but for each record I am currently getting only 1 error, assuming the record has 2 or more error -- I want to receive everything wrong with that record/item
case class Response(request: JsValue,
success: Boolean,
column: Option[String] = None,
message: String = "")
Here is validator, that return all errors in a json object
def validator(asJson: JsValue): Response = {
if (name == "")
return Response(asJson, false, Some("name"), "Name can't be blank")
if (age == -1 || age == "N/A")
return Response(asJson, false, Some("age"), "Age can't be blank")
if (DOB == -1 || DOB == "N/A" )
return Response(asJson, false, Some("DOB"), "DOB cannot be blank")
else
Response(asJson, true)
}
Currently if record1 doesn't have a name + age + DOB ---> I'm only getting "Name can't be blank"
How do I get (multiple errors per item instead of just 1 error per item):
Name can't be blank, Age can't be blank, DOB cannot be blank
This is just an outline of how it might work:
def validator(asJson: JsValue): Response = {
val errors = List(
if (name == "" ) Some("Name can't be blank") else None,
if (age == -1 || age == "N/A") Some("Age can't be blank") else None,
if (DOB == -1 || DOB == "N/A" ) Some("DOB cannot be blank") else None,
).flatten
if (errors.isEmpty) {
Response(asJson, true)
} else {
Response(asJson, false, errors.mkString(", "))
}
}
The errors value is created by making a List of Option values, one for each validation check. The flatten method extracts the contents of all the Some values and removes any None values. The result is a list of error strings.
The rest of the code formats the Response based on the list of errors.
If you are using Scala 2.13 then Option.when makes the tests shorter and clearer:
Option.when(name == "")("Name can't be blank"),
Option.when(age == -1 || age == "N/A")("Age can't be blank"),
Option.when(DOB == -1 || DOB == "N/A")("DOB cannot be blank"),
Here is an alternative using Validated that you might consider at some point
import play.api.libs.json._
import cats.data.ValidatedNec
import cats.implicits._
case class User(name: String, age: Int, dob: String)
case class UserDTO(name: Option[String], age: Option[Int], dob: Option[String])
implicit val userDtoFormat = Json.format[UserDTO]
val raw =
"""
|{
| "name": "Picard"
|}
|""".stripMargin
val userDto = Json.parse(raw).as[UserDTO]
def validateUser(userDto: UserDTO): ValidatedNec[String, User] = {
def validateName(user: UserDTO): ValidatedNec[String, String] =
user.name.map(_.validNec).getOrElse("Name is empty".invalidNec)
def validateAge(user: UserDTO): ValidatedNec[String, Int] =
user.age.map(_.validNec).getOrElse("Age is empty".invalidNec)
def validateDob(user: UserDTO): ValidatedNec[String, String] =
user.dob.map(_.validNec).getOrElse("DOB is empty".invalidNec)
(validateName(userDto), validateAge(userDto), validateDob(userDto)).mapN(User)
}
validateUser(userDto)
// res0: cats.data.ValidatedNec[String,User] = Invalid(Chain(Age is empty, DOB is empty))
Note the distinction between UserDTO ("data transfer object") which models whatever payload was sent over the wire, and User which models the actual data we require for our business logic. We separate the concern of user validation into its own method validateUser. Now we can work with valid user or errors like so
validateUserDTO(userDto) match {
case Valid(user) =>
// do something with valid user
case Invalid(errors) =>
// do something with errors
}
First of all, in scala you do not have the return statement, the last statement is returned (in fact it exists but its use is not recomended).
Secondly, in your code, I suppose you want to pass through several if statements. But if you return after each if statement you exit your function at the first condition that is true, and you will never go to the rest of the code.
If you want to gather several instances of Response, just gather them in a collection, and return the collection at the end of the function. A collection might be a List, or a Seq, or whatever.
In my code I've simple Validator that does exactly that:
abstract class Validator[T, ERR >: Null] {
def validate(a:T):Seq[ERR]
protected def is(errorCase:Boolean, err: => ERR) = if (errorCase) err else null
protected def check(checks:ERR*) = checks.collectFirst{ case x if x != null => x }.getOrElse(null)
protected def checkAll(checks:ERR*) = checks.filter(_ != null)
}
//implement checks on some types
val passwordValidator = new Validator[String, String] {
val universalPasswordInstruction = "Password needs to have at least one small letter, big letter, number and other sign."
def validate(a: String) = checkAll( //here we check all cases
is(a.length < 10, "Password should be longer than 10"),
check( //here we check only first one.
is(a.forall(_.isLetter), s"Passowrd has only letters. $universalPasswordInstruction"),
is(a.forall(_.isLetterOrDigit), s"Use at least one ascii character like '#' or '*'. $universalPasswordInstruction"),
),
is(a.contains("poop"), "Really!")
)
}
val userValidator = new Validator[(String, String), (Int, String)] {
override def validate(a: (String, String)): Seq[(Int, String)] = checkAll(
is(a._1.length > 0, (0, "Username is empty!")),
) ++ passwordValidator.validate(a._2).map(x => (1, x))
}
//use
userValidator.validate("SomeUserName" -> "SomePassword") match {
case Seq() => //OK case
case errors => //deal with it
}
Maybe not so fancy but works for me :). Code is simple and rather self explanatory. Nulls here are just implementation detail (they doesn't show up in usercode, and can be switched to Options).

passing variable argument data type to a scala method

I want to create a method/function in scala which can take variable arguments of type string or int and return the result of type String or Int.
def Hello(name: String, Param: int*/string*): Int/String= {
var index = 0
while(index < Param.length) {
var n = name
var ar = Param(index)
if ( n.equals(ar) ) return Param(index + 1)
else index = index + 1
}
return Param(index.length -1)
}
If we call the Hello function then it should return the result as given below.
val Res1 = Hello("Jack", "rakesh", 1, "Jack", 2, "No one")
println(Res1)
=> this should return value 2
val Res2 = Hello("ABC", "rakesh", 1, "Jack", 2, "Vik", 3, "ram", 4, "No one")
println(Res2)
=> this should return value "No one"
Using Any should work:
def hello(name: Any, param: Any*): Any= {
var list = param.dropWhile(_ != name)
list.drop(1).headOption.orElse(param.lastOption).getOrElse("")
}
Depending on how type safe you want it to be, you can try to use generics or other means to restrict the types used. Or you can just pattern match the response type:
hello("ABC", "rakesh", 1, "Jack", 2, "Vik", 3, "ram", 4, "No one") match {
case i: Int => println("Got a int:" + i)
case s: String=> println("Got a string:" + s)
}
This will help you
def Hello( name: String,args: Any* ) = {
val index = args.indexOf(name)
if(index == -1)
args(args.length - 1)
else
args(index + 1)
}
Your whole approach is faulty, but here's how it can be done in a type-safe manner.
def Hello(name: String, param: Either[Int,String]*): Either[Int,String] = {
param.sliding(2,2)
.find(_(0).fold(_ => false, _ == name))
.fold(param.last)(_(1))
}
Usage:
Hello("Jack", Right("rakesh"), Left(1), Right("Jack"), Left(2), Right("No one"))
// res0: Either[Int,String] = Left(2)
Hello("ABC", Right("rakesh"), Left(1), Right("Jack"), Left(2),
Right("Vik"), Left(3), Right("ram"), Left(4), Right("No one"))
// res1: Either[Int,String] = Right(No one)
But it would be better to rethink it from the ground up.
I believe, what you want to achieve, is to get an index of a String element(if start counting from 1) in varargs, or return "No one". No need to pass indices to the method. You can do it like this:
def hello(name: String, params: String*): Any = {
val idx = params.indexOf(name)
if (idx != -1) idx + 1 else "No One"
}
Unfortunately both this:
def Hello(name: String, args: Any* ) = {
val index = args.indexOf(name)
if(index == -1)
args(args.length - 1)
else
args(index + 1)
}
and this:
def hello(name: String, param: Any*): Any= {
var index = 0
while(index < param.length) {
var n = name
var ar = param(index)
if ( n.equals(ar) ) return param(index + 1)
else index = index + 1
}
param(index -1)
}
are broken, as they throw an exception if you try to find the index of "No one", as index + 1 will equal to the size of the array. And it's better to compare things in Scala with == for logical equality.
But it's better not to return Any at all, but return Option[Int]:
def hello(name: String, params: String*): Option[Int] =
Option(params.indexOf(name)).filter(_ != -1).map(_ + 1)
So then you can use it like this:
val message1 = hello("Jack", "rakesh" ,"Jack").getOrElse("No one")
val message2 = hello("ABC", "rakesh", "Jack", "Vik", "ram").getOrElse("No one")
Answering the comment:
I want to know how can i pass mixed datatypes to "param".
The simplest way is to have them all of type Any
and also get string or integer as return type
The same way, defining return type as Any
The only small issue here, is that there will be no compile time check against other types. E.g. somebody might pass Boolean or any complex object along with String's and Int's to your function. But you can check at runtime against it or play with types to limit them. I don't know your requirement here, maybe it's even advantage for you.
If having Any is fine, then I would solve it like this:
def Hello(name: Any, params: Any*): Any = Option(params)
.withFilter(_.nonEmpty)
.map(_.indexOf(name))
.filter(i => i != -1 && i < params.length - 1)
.map(i => params(i + 1))
.getOrElse("No one")
Or, if you can assume, params are never empty and you have to use the last param as the default, instead of just hard coded "No one":
def Hello(name: Any, params: Any*): Any = Option(params)
.withFilter(_.nonEmpty)
.map(_.indexOf(name))
.filter(i => i != -1 && i < params.length - 1)
.map(i => params(i + 1))
.getOrElse(params.last)
Notice the check against "No one" attack: i < params.length - 1.
Notice that name now is also of type Any.
Now, even if you pass "No one" as a name, the Option will evaluate to None thanking to the filter, and getOrElse will give you the default "No one" instead of an exception.

I want to pattern match from Array of String with a single String in scala?

val aggFilters = Array["IR*","IR_"]
val aggCodeVal = "IR_CS_BPV"
val flag = compareFilters(aggFilters,aggCodeVal)
As per my requirement I want to compare the patterns given in the aggFilters with aggCodeVal. The first pattern "IR*" is a match with "IR_CS_BPV" but not the second one, hence I want to break out of the for loop after the match is found so that I don't go for the second one "IR_". I don't want to use break statement like java.
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean = {
var flag: Boolean = false
for (aggFilter <- aggFilters) {
if (aggFilter.endsWith("*")
&& aggCodeVal.startsWith(aggFilter.substring(0, aggFilter.length() - 1))) {
flag = true
}
else if (aggFilter.startsWith("*")
&& aggCodeVal.startsWith(aggFilter.substring(1, aggFilter.length()))) {
flag = true
}
else if (((aggFilter startsWith "*")
&& aggFilter.endsWith("*"))
&& aggCodeVal.startsWith(aggFilter.substring(1, aggFilter.length() - 1))) {
flag = true
}
else if (aggFilter.equals(aggCodeVal)) {
flag = true
}
else {
flag = false
}
}
flag
}
If * is your only wild-card character, you should be able to leverage Regex to do your match testing.
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean =
aggFilters.exists(f => s"$f$$".replace("*",".*").r.findAllIn(aggCodeVal).hasNext)
You can use the built-in exists method to do it for you.
Extract a function that compares a single filter, with this signature:
def compareFilter(aggFilter: String, aggCodeVal: String): Boolean
And then:
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean = {
aggFilters.exists(filter => compareFilter(filter, aggCodeVal))
}
The implementation of compareFilter, BTW, can be shortened to something like:
def compareFilter(aggFilter: String, aggCodeVal: String): Boolean = {
(aggFilter.startsWith("*") && aggFilter.endsWith("*") && aggCodeVal.startsWith(aggFilter.drop(1).dropRight(1))) ||
(aggFilter.endsWith("*") && aggCodeVal.startsWith(aggFilter.dropRight(1))) ||
(aggFilter.startsWith("*") && aggCodeVal.startsWith(aggFilter.drop(1))) ||
aggFilter.equals(aggCodeVal)
}
But - double check me on that one, not sure I followed your logic perfectly.

Count the occurrence of a specific character in a string by using substring in Scala

I am very new to programming. And I'm being asked to count the occurrence of a specific character in a string by using substring and recursion in scala. I am totally lost and I don't know how to check the equality of the second character in that string. I am not supposed to use tailrecursion and map. Many thanks!
My code so far looks like this:
def countChars(str:String, chr:Char):Int = {
if (str.length == 0) 0
else {
if (chr == str.substring(0,1)) 1
else 0} + countChars()
}
println(countChars())
First of all, this is a working version (Haven't used the shortest version to make it easier to read):
def countChars(str: String, chr: Char): Int = {
if (str.length == 0) {
0
} else {
(if (chr.toString() == str.substring(0, 1)) {
1
} else {
0
}) + countChars(str.substring(1), chr)
}
}
println(countChars("Hello World", 'l')) //> 3
You had two problems. First you didn't call countChars with the right parameters. And more important and maybe not very obvious: You compared a Char with a String. This will never be true:
chr == str.substring(0,1)
Because == or equals are both checking the type first, this is different. Just use a typecast or in this case a simple toString like I did.
Hope this helps you.
Edit Sorry just pressed the post button accidentily.
Here's a solution using a match on the existence/value of the first character at each iteration:
def countChars(str: String, chr: Char): Int = str.headOption match {
case None => 0
case Some(ch) if ch == chr => 1 + countChars(str.substring(1), chr)
case _ => countChars(str.substring(1), chr)
}
The idiomatic scala solution would be:
def countChars(str: String, chr: Char): Int = str.filter(_ == chr).length
I don't know why you would use map, but your question didn't say not to use filter
A variation from #cokeSchlumpf and #Shadowlands answers,
def countChars(s: String, c: Char): Int = {
if (s.isEmpty) 0
else if (s.substring(0,1).head == c) 1 + countChars(s.substring(1),c)
else countChars(s.substring(1),c)
}