imposter icon indicating copy to clipboard operation
imposter copied to clipboard

Request to make Context.Request.toString() produce json

Open Ober3550 opened this issue 1 year ago • 2 comments

When I first got into the scripting side of imposter.sh I noticed that the JSObject for the context.request doesn't have many of the recommended functions for interfacing with Javascript. I first tried iterating across the queryParams but found that the keySet and the values iterator functions aren't available on the underlying Java objects. I created a dirty workaround by parsing the output of the toString() function since that's the only one available to get all of the data from the JSObject. However, obviously it would be better if I could turn the JSObject into a native Javascript one.

To me there's two options:

  1. Implement the keySet and values and other recommended functions on each class as an override
  2. Write a simple JSON serialiser and do JSON.parse(context.request.toString())

I've attached a simple Kotlin JSON serialiser but am curious to hear your opinion imposter/core/api/src/main/java/io/gatehill/imposter/script/JsonSerializer.kt

package io.gatehill.imposter.script
class JsonSerializer {
  companion object {
      @JvmStatic
      fun serialize(obj: Any?): String {
          return when (obj) {
              null -> "null"
              is String -> {
                  val escaped = obj.replace("\"", "\\\"")
                  "\"$escaped\""
              }
              is Map<*, *> -> serializeMap(obj)
              is List<*> -> serializeList(obj)
              else -> obj.toString()
          }
      }
      @JvmStatic
      fun serializeMap(map: Map<*, *>): String {
          val entries = map.entries.map { (key, value) ->
              "\"$key\": ${serialize(value)}"
          }
          return "{${entries.joinToString(", ")}}"
      }
      @JvmStatic
      fun serializeList(list: List<*>): String {
          val entries = list.map { serialize(it) }
          return "[${entries.joinToString(", ")}]"
      }
  }
}

imposter/core/api/src/main/java/io/gatehill/imposter/script/ExecutionContext.kt

override fun toString(): String {
    val request: Map<String, Any> = mapOf(
        "path" to path,
        "method" to method,
        "uri" to uri,
        "pathParams" to pathParams,
        "queryParams" to queryParams,
        "headers" to headers
    )
    return JsonSerializer.serialize(request)
}

PS. I'm aware there's a Graal plugin in the works

Ober3550 avatar Jan 17 '24 05:01 Ober3550

Hi @Ober3550, thanks very for much for raising this. s part of #557, we've done some work in the Graal engine that makes the Request object work like a regular JavaScript object. This means code like this gives sensible output:

print(JSON.stringify(context.request));

prints:

{"path":"/persons","method":"GET","uri":"http://localhost:8080/persons?foo=bar","headers":{"host":"localhost:8080","user-agent":"curl/8.4.0","accept":"*/*"},"pathParams":{},"queryParams":{"foo":"bar"},"formParams":{},"body":null,"normalisedHeaders":{"host":"localhost:8080","user-agent":"curl/8.4.0","accept":"*/*"}}

Similarly, you can now iterate over the request's members as you'd expect:

for (const queryParam in context.request.queryParams) {
    print(queryParam);
}

outofcoffee avatar May 07 '24 01:05 outofcoffee

@Ober3550 we've released v3.40.0, which has the improved js-graal plugin. This one has new plumbing for Graal to understand how to parse objects better.

outofcoffee avatar May 19 '24 01:05 outofcoffee