Adding some custom DSL for session management

I’m working on adding some syntactic sugar to the Gatling DSL to handle the kinds of things that I find myself doing often. Some examples:

`
scenario( name )
.exec(
http( desc )
.get( path )
.check( jsonPath( “$.result[*]” ).saveAs( OBJECT_LIST ) )
)
// pick one of the objects at random
.set( OBJECT_JSON ).from( OBJECT_LIST ) // I may change the syntax to make it more obvious
.extract( “$.id” ).from( $(OBJECT_JSON) ).into( OBJECT_ID )

`

I’m following the “pimp my library pattern” that worked before. But now it is not working correctly, and I’m stumped as to why.

`
import scala.util.Random

import io.gatling.core.session.{ Expression, Session }
import io.gatling.core.Predef._
import io.gatling.core.validation._
import io.gatling.core.structure.ChainBuilder
import io.gatling.core.json.Boon
import io.gatling.core.check.extractor.jsonpath._

object SessionManagement {

implicit class SessionManagementExtensions( val c : ChainBuilder ) {

trait SetSessionVariableAPI {
def from( src: String ) : ChainBuilder
def to[T]( value: Expression[T] ) : ChainBuilder
}

def set( dest: String ) = new SetSessionVariableAPI {

def from( src: String ) =
c.exec( session => {
val list = session( src ).as[Vector[String]]
val i = if ( list.size == 0 ) -1 else Random.nextInt( list.size )
val value = if ( i > 0 ) list(i) else “INVALID_” + dest
session.set( dest, value )
})

def to[T]( value: Expression[T] ) =
c.exec( session => session.set( dest, value(session) ) )

}

trait ExtractFromInto { def into( name: String ) : ChainBuilder }
trait ExtractFrom { def from( json: Expression[String] ) : ExtractFromInto }

def extract( path: String ) = new ExtractFrom {
def from( json: Expression[String] ) = new ExtractFromInto {
def into( name: String ) =
c.exec( session => {
val parsed = Boon.parse( json(session).toString )
session.set( name, JsonPathExtractor.extractAll[String]( parsed, path ).get )
})
}
}

}

}
`

This seems to compile (which doesn’t mean I did it right, but at least it compiles), but when I try to use it, I get an error:

11:56:52.169 [ERROR] i.g.a.ZincCompiler$ - /src/rtde-testing/performance/rtde/simulations/com/cigna/rtde/scenarios/sandbox.scala:17: value set is not a member of io.gatling.core.structure.ChainBuilder possible cause: maybe a semicolon is missing beforevalue set’?
11:56:52.172 [ERROR] i.g.a.ZincCompiler$ - .set( “FOO” ).to(“bar”)
11:56:52.173 [ERROR] i.g.a.ZincCompiler$ - ^
`

Can you see what I’m doing wrong that might cause me to get this kind of an error?

You bootstrapped from a scenario, so you get a chain of ScenarioBuilders, when you can call “inject” at some point.

ScenarioBuilder doesn’t extend ChainBuilder. Both extend StructureBuilder.

That was one of my issues. I’d love to make it work for either/or, and if that is easy to do, I’d love to know how to do it.

The other problem was, in order to use the implicit, I have to import SessionManagement._. I’m sufficiently new to this that this requirement did not occur to me right away.

I have .set(VAR).to(VAL) working, including with session expansion of VAL.
I have .set(VAR).from(LIST) working, also,
And I just finished making .extract(PATH).from(JSON).into(VAL) work

I you have the time, I’d love some feedback on my code. Style, approach, scala-esqueness, whatever. :slight_smile:

`
object SessionManagement {

implicit class SessionManagementExtensions( val c : ChainBuilder ) {

def evaluate( session: Session, string: Expression[String] ) : Any = string(session) match {
case Success(x) => x
case Failure(msg) => throw new Error(msg)
}

trait SetSessionVariableAPI {

def from( src: String ) : ChainBuilder
def to( value: Any ) : ChainBuilder
}

def set( dest: String ) = new SetSessionVariableAPI {

def from( src: String ) =
c.exec( session => {
session.set( dest, session.attributes(src) match {
case s:String => s
case list:Vector[String] => {
val i = if ( list.size == 0 ) -1 else Random.nextInt( list.size )
if ( i >= 0 ) list(i)
else “INVALID_” + dest
}
}
)
} )

def to( value: Any ) =
c.exec( session => session.set( dest, value match {
case s:String => evaluate( session, s )
case _ => value
} ) )
}

trait ExtractFromInto { def into( name: String ) : ChainBuilder }
trait ExtractFrom { def from( json: String ) : ExtractFromInto }

def extract( path: String ) = new ExtractFrom {
def from( json: String ) = new ExtractFromInto {
def into( name: String ) =
c.exec( session => {
val parsed = Boon.parse( evaluate( session, json ).toString )
session.set( name,
JsonPathExtractor.extractAll[String]( parsed, path ) match {
case Success(x) => {
if ( x.isEmpty ) “”
else {
val list = x.toList
if ( list.size > 1 ) list.toVector
else list.head
}
}
case Failure(msg) => throw new Error(msg)
}
)
})
}
}
}
}
`

Not found of set/to and set/from. It looks like the same thing to me. And I would name it set/as.

And I would use Gatling Expression/EL instead of introducing some different behavior, like your random thing. There’s a “.random()” in Gatling EL that does this job.

import io.gatling.core.structure.StructureBuilder
implicit class SessionManagementExtensions[T <: StructureBuilder[T]](val c: T) { // see T here? proper way to have it work with both ChainBuilder and ScenarioBuilder

def set(dest: String) = new {

def as(value: Expression[Any]): T =
c.exec { session =>
value(session).map(v => session.set(dest, v))
}
}
}

This way, you can write:
.set(“foo”).as(“bar”)
.set(“foo”).as(2)
.set(“foo”).as("${bar}")
.set(“foo”).as("${bar.random()}")

I much prefer your implementation of “as” over my implementation of “to” - much cleaner and shorter, like I figured it should be.

And I agree about set/from and set/to being unclear. I hadn’t settled on the API yet, I was just trying to prove I could do it at all.

When I first decided to implement .from() I had not yet discovered that EL has a .random() syntax. I would like to do it that way, but because I use constants in place of session variable names, that complicates things a touch.

Instead of “${bar}” I write $(BAR) which converts to “${bar}”, but my session variable name is checked at compile time. To get the benefits of the EL random, I would need a syntax for how to express it using constants. But I am torn on what that should look like:

.set( SOME_VAR ).as( random( LIST ) )

.set( SOME_VAR ).as( randomFrom( LIST ) )

.set( SOME_VAR ).as( randomOneOf( LIST ) )
.set( SOME_VAR ).as( oneOf( LIST ) )

.set( SOME_VAR ).as( anyOneOf( LIST ) )

.set( SOME_VAR ).as( pickFrom( LIST ) )

.set( SOME_VAR ).as( pickOne( LIST ) )

.set( SOME_VAR ).as( chooseFrom( LIST ) )

.set( SOME_VAR ).as( chooseOne( LIST ) )

Do you have an opinion on what it should be? :slight_smile:

Oh, by the way, what does .random() do if the element in question is a single value and not a vector/list?

a Failure, as expected. You’re not supposed to call random on something that’s not a Seq.