Then, you’ve been very theoretical so far and described a technical solution (case classes, expressions) you have in mind.
What’s your need exactly? You might just be heading the wrong direction.
True, that can very much be the case, haha. So here’s some context:
I’m building a testing framework to load test an api server. All endpoints are RESTful, and my strategy is to build a bunch of Gatling endpoints that much up to the server’s endpoints. Then I can create scenarios using Gatling that mirror real-world use cases. I decided to break each endpoint into 3 parts: Dependencies on other endpoints, OAuth, and the actual request. Most requests (but not all) require OAuth. These are my main concerns:
- Easily modify the guts of the request while minimizing code copy-n-paste. This includes specifying the action (get, patch, update etc) and the types of params (body, url, form, etc)
- Easily specify Dependencies a request relies on. A Dependency is another endpoint that in one way or another sets session variable that this request uses.
- Easily execute OAuth
- For any endpoint, be able to omit Dependency or OAuth execution.
Here’s my current structure:
abstract class Request extends Requestable represents a Request which contains the meat of the request (url, name, params, etc…). To create Gatling endpoints, I create objects which extend Request. The use case is that I can then simply call exec(Endpoint) to use an endpoint.
Requestable is a trait which has
var request: ChainBuilder
def executable: ChainBuilder
var dependentParams: Set[String]
var paramsSet: Set[String]
The idea behind Requestable is it represents a request to execute. There’s an implicit conversion from Requestable → ChainBuilder via executable.
What is really tripping me up is how I’m resolving dependencies. Currently, the abstract Request class has a case class Dependencies(request: Request) extends Requestable. The input is used to getDependencies(request, session) so that it can execute each. getDependencies simply returns a Seq[Requestable] representing a sequence of endpoints that should be executed before the actual request is executed. Here’s the Dependencies case class:
case class Dependencies(req: Request) extends Requestable {
request =
// Anytime an endpoint is added, it should also be placed here. This allows it to be executed as a Dependency.
// Currently, the builder runs into an infinite loop since each endpoint invariably points to itself in dependencies...
foreach(session => DependencyManager.getDependencies(req, session), "dependency") {
doSwitch("${dependency}") (
CreateUser -> CreateUser,
GetUserInformation -> GetUserInformation,
UpdateUserInformation -> UpdateUserInformation
)
}
}
Request has these methods which utilize the case classes. And remember, request is a var in Requestable, which is the ChainBuilder that represents the actual request.
protected final def dependencies: Dependencies = Dependencies(this)
protected final def oauth = OAuth(this)
To give an example of how I then create an endpoint:
object UpdateUserInformation extends Request {
jsonBody = (session: Session) =>
s"""
>"user":${session("user").as[String] + "a"},
>"email":"newEmail${session("user").as[String]}@something.com",
>"pass":"fooPass"
""".stripMargin
paramsSet = Set()
dependentParams = Set("user", "email", "pass", "userId")
addDependency(this)
requestName = "Update User Information"
url = "foo"
request = exec(
checkRequest(
http(requestName)
.patch(session => baseUrl + url + session("userId").as[String])
.header("Authorization", "${accessToken}")
)
).pause(Environments.shortPause)
protected override def checkRequest(request: HttpRequestBuilder): HttpRequestBuilder = {
request.check(
status.is(200),
jsonPath("$.user").saveAs("user"),
jsonPath("$.email").optional.saveAs("email"),
jsonPath("$.pass").optional.saveAs("pass")
)
}
}
in this case, executable is not overridden, and by default is exec(dependencies).exec(oauth).exec(request)
Ok, so that’s a lengthy explanation but I’d rather give more context rather than less. I do appreciate your help. My background is mostly object-oriented–Java and C#–so I tend to think very much in that mindset. There may be a simple solution I’m missing. If so, I’d love to know 