Skip to content

Commit

Permalink
Introduce Resource, reuse Graal's context
Browse files Browse the repository at this point in the history
  • Loading branch information
danslapman committed Apr 1, 2024
1 parent 8a7d3f7 commit a929f65
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ final class PublicApiHandler(
stub.response
.applyIf(_.isTemplate)(
HttpStubResponse.jsonBody
.updateF(_.substitute(data).substitute(xdata))
.andThen(HttpStubResponse.xmlBody.updateF(_.substitute(data).substitute(xdata)))
.updateF(_.substitute(data).map(_.substitute(xdata)).use(_.pure[Id]))
.andThen(HttpStubResponse.xmlBody.updateF(_.substitute(data).map(_.substitute(xdata)).useAsIs))
)
.applyIf(HttpStubResponse.headers.getOption(_).exists(_.values.exists(_.isTemplate)))(
HttpStubResponse.headers.updateF(_.view.mapValues(_.substitute(data, xdata)).toMap)
Expand Down Expand Up @@ -250,7 +250,7 @@ final class PublicApiHandler(
.filterNot(h => proxyConfig.excludedResponseHeaders(h.name))
.map(h => h.name -> h.value)
.toMap,
jsonResponse.patch(data, patch).noSpaces,
jsonResponse.patch(data, patch).use(_.noSpaces),
delay
)
case Left(error) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ class GrpcRequestHandlerImpl(
response <- stub.response match {
case FillResponse(rdata, delay) =>
ZIO.when(delay.isDefined)(ZIO.sleep(Duration.fromScala(delay.get))) *>
ZIO.attemptBlocking(responseSchema.parseFromJson(rdata.substitute(data), stub.responseClass))
ZIO.attemptBlocking(responseSchema.parseFromJson(rdata.substitute(data).useAsIs, stub.responseClass))
case GProxyResponse(endpoint, patch, delay) =>
for {
_ <- ZIO.when(delay.isDefined)(ZIO.sleep(Duration.fromScala(delay.get)))
binaryResp <- proxyCall(endpoint, bytes)
jsonResp <- responseSchema.convertMessageToJson(binaryResp, stub.responseClass)
patchedJsonResp = jsonResp.patch(data, patch)
patchedJsonResp = jsonResp.patch(data, patch).useAsIs
patchedBinaryResp = responseSchema.parseFromJson(patchedJsonResp, stub.responseClass)
} yield patchedBinaryResp
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import ru.tinkoff.tcb.utils.transformation.xml.*

object Substitute {
implicit def jsonSJson(implicit sandbox: GraalJsSandbox): Substitute[Json, Json] = (a: Json, b: Json) =>
a.substitute(b)
a.substitute(b).useAsIs
implicit def jsonSNode(implicit sandbox: GraalJsSandbox): Substitute[Json, KNode] = (a: Json, b: KNode) =>
a.substitute(b)
implicit def nodeSJson(implicit sandbox: GraalJsSandbox): Substitute[Node, Json] = (a: Node, b: Json) =>
a.substitute(b)
a.substitute(b).useAsIs
implicit def nodeSnode(implicit sandbox: GraalJsSandbox): Substitute[Node, KNode] = (a: Node, b: KNode) =>
a.substitute(b)
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ final class ScenarioEngine(
.method(Method(request.method.entryName), uri"$requestUrl")
.pipe(r =>
request match {
case JsonCallbackRequest(_, _, _, body) => r.body(body.substitute(data).substitute(xdata).noSpaces)
case JsonCallbackRequest(_, _, _, body) => r.body(body.substitute(data).map(_.substitute(xdata)).use(_.noSpaces))
case XMLCallbackRequest(_, _, _, body) =>
r.body(body.toNode.substitute(data).substitute(xdata).mkString)
r.body(body.toNode.substitute(data).map(_.substitute(xdata)).use(_.mkString))
case _ => r
}
)
Expand Down Expand Up @@ -178,18 +178,18 @@ final class ScenarioEngine(
out match {
case RawOutput(payload, _) => payload
case JsonOutput(payload, _, isT) =>
if (isT) payload.substitute(data).substitute(xdata).noSpaces else payload.noSpaces
if (isT) payload.substitute(data).map(_.substitute(xdata)).use(_.noSpaces) else payload.noSpaces
case XmlOutput(payload, _, isT) =>
if (isT) payload.toNode.substitute(data).substitute(xdata).mkString else payload.asString
if (isT) payload.toNode.substitute(data).map(_.substitute(xdata)).use(_.mkString) else payload.asString
}
)
} { drb =>
val bodyJson = out match {
case RawOutput(payload, _) => Json.fromString(payload)
case JsonOutput(payload, _, isT) => if (isT) payload.substitute(data).substitute(xdata) else payload
case JsonOutput(payload, _, isT) => if (isT) payload.substitute(data).map(_.substitute(xdata)).useAsIs else payload
case XmlOutput(payload, _, isT) =>
if (isT)
Json.fromString(payload.toNode.substitute(data).substitute(xdata).mkString)
Json.fromString(payload.toNode.substitute(data).map(_.substitute(xdata)).use(_.mkString))
else Json.fromString(payload.asString)
}

Expand All @@ -201,7 +201,7 @@ final class ScenarioEngine(
"_message" := bodyJson.asString.map(b64Enc).getOrElse(b64Enc(bodyJson.noSpaces))
)
else Json.obj("_message" := bodyJson)
)
).useAsIs
)
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ru.tinkoff.tcb.utils.sandboxing

import io.circe.Json

import scala.util.Try

trait CodeRunner {
def eval(code: String): Try[Json]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package ru.tinkoff.tcb.utils.sandboxing

import scala.util.Try
import scala.util.Using
import scala.util.chaining.*

import io.circe.Json
import org.graalvm.polyglot.*

import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig
import ru.tinkoff.tcb.utils.resource.Resource
import ru.tinkoff.tcb.utils.sandboxing.conversion.*

class GraalJsSandbox(
Expand All @@ -32,6 +34,22 @@ class GraalJsSandbox(
preludeSource.foreach(context.eval)
context.eval("js", code).toJson
}.flatten

def makeRunner(environment: Map[String, GValue]): Resource[CodeRunner] =
Resource.make(
Context
.newBuilder("js")
.allowHostAccess(HostAccess.ALL)
.allowHostClassLookup((t: String) => allowedClasses(t))
.option("engine.WarnInterpreterOnly", "false")
.build().tap { context =>
context.getBindings("js").pipe { bindings =>
for ((key, value) <- environment.view.mapValues(_.unwrap))
bindings.putMember(key, value)
}
preludeSource.foreach(context.eval)
}
)(_.close()).map(ctx => (code: String) => ctx.eval("js", code).toJson)
}

object GraalJsSandbox {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ru.tinkoff.tcb.utils.circe.*
import ru.tinkoff.tcb.utils.circe.optics.JsonOptic
import ru.tinkoff.tcb.utils.json.json2StringFolder
import ru.tinkoff.tcb.utils.regex.OneOrMore
import ru.tinkoff.tcb.utils.resource.Resource
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
import ru.tinkoff.tcb.utils.sandboxing.conversion.circe2js
import ru.tinkoff.tcb.utils.transformation.xml.nodeTemplater
Expand All @@ -31,32 +32,34 @@ package object json {
}
}

def jsonTemplater(values: Json)(implicit sandbox: GraalJsSandbox): PartialFunction[String, Json] = {
def jsonTemplater(values: Json)(implicit sandbox: GraalJsSandbox): Resource[PartialFunction[String, Json]] = {
lazy val jsData = values.asObject.map(_.toMap.view.mapValues(_.foldWith(circe2js)).toMap).getOrElse(Map())

{
case JOptic(None, optic) if optic.validate(values) =>
optic.get(values)
case JOptic(Some(":"), optic) if optic.validate(values) =>
optic.get(values).pipe(j => castToString.applyOrElse(j, (_: Json) => j))
case JOptic(Some("~"), optic) if optic.validate(values) =>
optic.get(values).pipe(j => castFromString.applyOrElse(j, (_: Json) => j))
case str @ JORxs() =>
Json.fromString(
JORx.replaceSomeIn(
str,
m =>
JsonOptic
.fromPathString(m.group(2))
.getOpt(values)
.map(_.foldWith(json2StringFolder))
sandbox.makeRunner(jsData).map[PartialFunction[String, Json]] { runner =>
{
case JOptic(None, optic) if optic.validate(values) =>
optic.get(values)
case JOptic(Some(":"), optic) if optic.validate(values) =>
optic.get(values).pipe(j => castToString.applyOrElse(j, (_: Json) => j))
case JOptic(Some("~"), optic) if optic.validate(values) =>
optic.get(values).pipe(j => castFromString.applyOrElse(j, (_: Json) => j))
case str @ JORxs() =>
Json.fromString(
JORx.replaceSomeIn(
str,
m =>
JsonOptic
.fromPathString(m.group(2))
.getOpt(values)
.map(_.foldWith(json2StringFolder))
)
)
)
case CodeRx(code) =>
sandbox.eval(code, jsData) match {
case Success(value) => value
case Failure(exception) => throw exception
}
case CodeRx(code) =>
runner.eval(code) match {
case Success(value) => value
case Failure(exception) => throw exception
}
}
}
}

Expand Down Expand Up @@ -85,8 +88,8 @@ package object json {
def transformValues(f: PartialFunction[Json, Json]): TailRec[Json] =
transformValues(j => f.applyOrElse(j, (_: Json) => j))

def substitute(values: Json)(implicit sandbox: GraalJsSandbox): Json =
jsonTemplater(values).pipe { templater =>
def substitute(values: Json)(implicit sandbox: GraalJsSandbox): Resource[Json] =
jsonTemplater(values).map { templater =>
transformValues { case js @ JsonString(str) =>
templater.applyOrElse(str, (_: String) => js)
}.result
Expand All @@ -107,8 +110,8 @@ package object json {
}
}.result

def patch(values: Json, schema: Map[JsonOptic, String])(implicit sandbox: GraalJsSandbox): Json =
jsonTemplater(values).pipe { templater =>
def patch(values: Json, schema: Map[JsonOptic, String])(implicit sandbox: GraalJsSandbox): Resource[Json] =
jsonTemplater(values).map { templater =>
schema.foldLeft(j) { case (acc, (optic, defn)) =>
templater.lift.apply(defn).fold(acc)(optic.set(_)(acc))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ package object string {

def substitute(jvalues: Json, xvalues: Node)(implicit sandbox: GraalJsSandbox): String =
if (SubstRx.findFirstIn(s).isDefined || CodeRx.findFirstIn(s).isDefined)
Json.fromString(s).substitute(jvalues).substitute(xvalues).asString.getOrElse(s)
Json.fromString(s).substitute(jvalues).map(_.substitute(xvalues)).useAsIs.asString.getOrElse(s)
else s

def substitute(values: Json)(implicit sandbox: GraalJsSandbox): String =
if (SubstRx.findFirstIn(s).isDefined || CodeRx.findFirstIn(s).isDefined)
Json.fromString(s).substitute(values).asString.getOrElse(s)
Json.fromString(s).substitute(values).useAsIs.asString.getOrElse(s)
else s
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kantan.xpath.implicits.*

import ru.tinkoff.tcb.utils.json.json2StringFolder
import ru.tinkoff.tcb.utils.regex.OneOrMore
import ru.tinkoff.tcb.utils.resource.Resource
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
import ru.tinkoff.tcb.utils.transformation.json.jsonTemplater
import ru.tinkoff.tcb.xpath.*
Expand Down Expand Up @@ -102,8 +103,8 @@ package object xml {
}.result
}

def substitute(values: Json)(implicit sandbox: GraalJsSandbox): Node =
jsonTemplater(values).pipe { templater =>
def substitute(values: Json)(implicit sandbox: GraalJsSandbox): Resource[Node] =
jsonTemplater(values).map { templater =>
transform {
case elem: Elem =>
elem.attributes.foldLeft(elem)((e, attr) =>
Expand Down Expand Up @@ -135,27 +136,29 @@ package object xml {

def patchFromValues(jValues: Json, xValues: Node, schema: Map[XmlZoom, String])(implicit
sandbox: GraalJsSandbox
): Node = {
val jt = jsonTemplater(jValues)
val nt = nodeTemplater(<wrapper>{xValues}</wrapper>)

schema
.foldLeft(<wrapper>{n}</wrapper>: Node) { case (acc, (zoom, defn)) =>
defn match {
case jp if jt.isDefinedAt(jp) =>
(zoom ==> Replace(_.map(_.transform { case Text(_) => Text(jt(jp).foldWith(json2StringFolder)) }.result)))
.transform[Try](acc)
.map(_.head)
.getOrElse(acc)
case xp if nt.isDefinedAt(xp) =>
(zoom ==> Replace(_.map(_.transform { case Text(_) => Text(nt(xp)) }.result)))
.transform[Try](acc)
.map(_.head)
.getOrElse(acc)
case _ => acc
): Resource[Node] = {
for {
jt <- jsonTemplater(jValues)
nt = nodeTemplater(<wrapper>{xValues}</wrapper>)
} yield {
schema
.foldLeft(<wrapper>{n}</wrapper>: Node) { case (acc, (zoom, defn)) =>
defn match {
case jp if jt.isDefinedAt(jp) =>
(zoom ==> Replace(_.map(_.transform { case Text(_) => Text(jt(jp).foldWith(json2StringFolder)) }.result)))
.transform[Try](acc)
.map(_.head)
.getOrElse(acc)
case xp if nt.isDefinedAt(xp) =>
(zoom ==> Replace(_.map(_.transform { case Text(_) => Text(nt(xp)) }.result)))
.transform[Try](acc)
.map(_.head)
.getOrElse(acc)
case _ => acc
}
}
}
.pipe(_.child.head)
.pipe(_.child.head)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue
"composite" := "Main topic: Some description"
)

val sut = template.substitute(values)
val sut = template.substitute(values).useAsIs

sut shouldBe expected

val sut2 = template2.substitute(values)
val sut2 = template2.substitute(values).useAsIs

sut2 shouldBe expected
}
Expand All @@ -82,7 +82,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue
"value" := "${description}"
)

val sut = template.substitute(Json.obj())
val sut = template.substitute(Json.obj()).useAsIs

sut shouldBe template
}
Expand All @@ -92,7 +92,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue

val template = Json.obj("value" := "${message}")

val sut = template.substitute(Json.obj("message" := Json.obj("peka" := "name")))
val sut = template.substitute(Json.obj("message" := Json.obj("peka" := "name"))).useAsIs

sut shouldBe Json.obj("value" := Json.obj("peka" := "name"))
}
Expand All @@ -114,7 +114,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue
"n" := 45.99
)

val sut = template.substitute(values)
val sut = template.substitute(values).useAsIs

sut shouldBe Json.obj(
"a" := "true",
Expand All @@ -140,7 +140,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue
"n" := "45.99"
)

val sut = template.substitute(values)
val sut = template.substitute(values).useAsIs

sut shouldBe Json.obj(
"a" := true,
Expand Down Expand Up @@ -267,7 +267,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue
JsonOptic.fromPathString("o3.client") -> "${name} ${surname}"
)

val sut = target.patch(source, schema)
val sut = target.patch(source, schema).useAsIs

sut.get(JLens \ "a2" \ 4).asString.value shouldBe "nondesc"
sut.get(JLens \ "o3" \ "client").asString.value shouldBe "Peka Kekovsky"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class XmlTransformationSpec extends AnyFunSuite with Matchers {
)
)

val sut = template.substitute(data)
val sut = template.substitute(data).useAsIs

sut shouldBe <root rt="kek"><tag1 t1="a1">test</tag1><tag2 t2="a2">42</tag2><composite cmp="kek_a2">test_42</composite></root>
}
Expand Down Expand Up @@ -138,7 +138,7 @@ class XmlTransformationSpec extends AnyFunSuite with Matchers {
XmlZoom.fromXPath("/root/second").toOption.get -> "${/data/value}"
)

val sut = target.patchFromValues(source, xSource, schema)
val sut = target.patchFromValues(source, xSource, schema).useAsIs

info(sut.toString())

Expand Down
Loading

0 comments on commit a929f65

Please sign in to comment.