Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Jackson serde #10035

Merged
merged 20 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 28 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ lazy val enso = (project in file("."))
`json-rpc-server`,
`language-server`,
`polyglot-api`,
`polyglot-api-serde`,
`project-manager`,
`syntax-definition`,
`syntax-rust-definition`,
Expand Down Expand Up @@ -1422,7 +1423,8 @@ lazy val `engine-common` = project
Test / javaOptions ++= Seq(
),
libraryDependencies ++= Seq(
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided"
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.28.5" % "provided"
hubertp marked this conversation as resolved.
Show resolved Hide resolved
)
)
.dependsOn(testkit % Test)
Expand All @@ -1444,11 +1446,14 @@ lazy val `polyglot-api` = project
"runtime-fat-jar"
) / Compile / fullClasspath).value,
libraryDependencies ++= Seq(
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"com.google.flatbuffers" % "flatbuffers-java" % flatbuffersVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"com.google.flatbuffers" % "flatbuffers-java" % flatbuffersVersion,
"io.circe" %% "circe-core" % circeVersion,
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.28.5",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.28.5",
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test
),
libraryDependencies ++= jackson,
GenerateFlatbuffers.flatcVersion := flatbuffersVersion,
Expand All @@ -1460,6 +1465,20 @@ lazy val `polyglot-api` = project
.dependsOn(`logging-utils`)
.dependsOn(testkit % Test)

lazy val `polyglot-api-serde` = project
.in(file("engine/polyglot-api-serde"))
.settings(
frgaalJavaCompilerSetting,
Test / fork := true,
commands += WithDebugCommand.withDebug,
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.28.5",
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test
)
)
.dependsOn(`polyglot-api`)

lazy val `language-server` = (project in file("engine/language-server"))
.enablePlugins(JPMSPlugin)
.settings(
Expand Down Expand Up @@ -1604,6 +1623,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
.dependsOn(`logging-utils-akka`)
.dependsOn(`logging-service`)
.dependsOn(`polyglot-api`)
.dependsOn(`polyglot-api-serde`)
.dependsOn(`searcher`)
.dependsOn(`text-buffer`)
.dependsOn(`version-output`)
Expand Down Expand Up @@ -1866,6 +1886,7 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`library-manager`)
.dependsOn(`logging-truffle-connector`)
.dependsOn(`polyglot-api`)
.dependsOn(`polyglot-api-serde`)
.dependsOn(`text-buffer`)
.dependsOn(`runtime-compiler`)
.dependsOn(`runtime-suggestions`)
Expand Down Expand Up @@ -2515,6 +2536,7 @@ lazy val `engine-runner` = project
.dependsOn(`logging-service`)
.dependsOn(`logging-service-logback` % Runtime)
.dependsOn(`polyglot-api`)
.dependsOn(`polyglot-api-serde`)

lazy val buildSmallJdk =
taskKey[File]("Build a minimal JDK used for native image generation")
Expand Down
57 changes: 57 additions & 0 deletions engine/common/src/main/scala/org/enso/common/Serde.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.enso.common


import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

import java.io.File

object Serde {

val config = CodecMakerConfig
.withAllowRecursiveTypes(allowRecursiveTypes = true)
.withRequireCollectionFields(requireCollectionFields = true)
.withTransientEmpty(false)

implicit lazy val fileCodec: JsonValueCodec[File] = new JsonValueCodec[File] {
override def decodeValue(in: JsonReader, default: File): File = {
val t = in.nextToken()

if (t == 'n') in.readNullOrError(null, "expected 'null' or JSON value")
else if (t == '"') {
in.rollbackToken()
val path = in.readString(null)
if (path == null) null
else new File(path)
} else if (t == '{') {
if (!in.isNextToken('}')) {
in.rollbackToken()
val key = in.readKeyAsString()
if (key != "file") {
throw new RuntimeException("invalid field name, expected `file` got `" + key + "`")
}
val path = in.readString(null)
if (!in.isNextToken('}')) {
in.objectEndOrCommaError()
}
new File(path)
} else {
null
}

} else throw new RuntimeException("Invalid value, cannot deserialize at " + t)
}

override def encodeValue(x: File, out: JsonWriter): Unit = {
out.writeObjectStart()
if (x == null) out.writeNull()
else {
out.writeKey("file")
out.writeVal(x.getPath)
}
out.writeObjectEnd()
}

override def nullValue: File = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import org.enso.logger.akka.ActorMessageLogging
import org.enso.logger.masking.ToLogString
import org.enso.polyglot.runtime.Runtime
import org.enso.polyglot.runtime.Runtime.{Api, ApiEnvelope}
import org.enso.polyglot.runtime.serde.ApiSerializer
import org.graalvm.polyglot.io.MessageEndpoint

import java.nio.ByteBuffer

import scala.util.{Failure, Success}

/** An actor managing a connection to Enso's runtime server. */
Expand Down Expand Up @@ -90,7 +90,7 @@ final class RuntimeConnector(
context.stop(self)

case msg: Runtime.ApiEnvelope =>
engine.sendBinary(Runtime.Api.serialize(msg))
engine.sendBinary(ApiSerializer.serialize(msg))

msg match {
case Api.Request(Some(id), _) =>
Expand Down Expand Up @@ -178,7 +178,7 @@ object RuntimeConnector {
override def sendText(text: String): Unit = {}

override def sendBinary(data: ByteBuffer): Unit =
Runtime.Api.deserializeApiEnvelope(data) match {
ApiSerializer.deserializeApiEnvelope(data) match {
case Success(msg) =>
actor ! MessageFromRuntime(msg)
case Failure(ex) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.enso.polyglot.runtime.serde

import org.enso.polyglot.runtime.Runtime.ApiEnvelope
import java.nio.ByteBuffer
import com.github.plokhotnyuk.jsoniter_scala.core._

import scala.util.Try

object ApiSerializer {

/** Serializes an ApiEnvelope into a byte buffer.
*
* @param message the message to serialize.
* @return the serialized version of the message.
*/
def serialize(message: ApiEnvelope): ByteBuffer = {
ByteBuffer.wrap(writeToArray(message))
}

/** Deserializes a byte buffer into an ApiEnvelope, which can be a Request
* or a Response.
*
* @param bytes the buffer to deserialize
* @return the deserialized message, if the byte buffer can be deserialized.
*/
def deserializeApiEnvelope(bytes: ByteBuffer): Try[ApiEnvelope] =
Try(readFromByteBuffer[ApiEnvelope](bytes))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package org.enso.polyglot.runtime.serde

import java.util.UUID

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import org.enso.polyglot.Suggestion
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api.SuggestionAction
import org.enso.polyglot.runtime.Runtime.{Api, ApiEnvelope}

class SerdeSpec extends AnyFlatSpec with Matchers {

it should "serialize and deserialize API messages in JSON" in {
val message: ApiEnvelope = Api.Response(
Api.SuggestionsDatabaseModuleUpdateNotification(
module = "Dummy",
actions = Vector.empty,
exports = Vector.empty,
updates = Tree.Root(
children = Vector(
Tree.Node(
element = Api.SuggestionUpdate(
suggestion = Suggestion
.Module("local.New_Project_1.Main", documentation = None),
action = SuggestionAction.Add()
),
children = Vector.empty
),
Tree.Node(
element = Api.SuggestionUpdate(
suggestion = Suggestion
.DefinedMethod(
externalId = Some(UUID.randomUUID()),
module = "local.New_Project_1.Main",
name = "main",
arguments = Seq.empty,
selfType = "local.New_Project_1.Main",
returnType = "Standard.Base.Any.Any",
isStatic = true,
documentation = None,
annotations = Seq.empty
),
action = SuggestionAction.Add()
),
children = Vector(
Tree.Node(
element = Api.SuggestionUpdate(
suggestion = Suggestion
.Local(
externalId = Some(UUID.randomUUID()),
module = "local.New_Project_1.Main",
name = "main",
returnType = "Standard.Base.Any.Any",
scope = Suggestion.Scope(
Suggestion.Position(0, 1),
Suggestion.Position(2, 3)
),
documentation = None
),
action = SuggestionAction.Add()
),
children = Vector.empty
),
Tree.Node(
element = Api.SuggestionUpdate(
suggestion = Suggestion
.Type(
externalId = Some(UUID.randomUUID()),
module = "Standard.Base.Data.Set",
name = "Set",
params =
Seq.empty, // Set(Suggestion.Argument("foo", "bar", true, false, None, None)),
returnType = "Standard.Base.Data.Set.Set",
parentType = Some("Standard.Base.Any.Any"),
documentation =
None //Some(" An unordered collection of unique values")
//reexports = Set("foo")
),
action = SuggestionAction.Modify(documentation = Some(None))
),
children = Vector.empty
),
Tree.Node(
element = Api.SuggestionUpdate(
suggestion = Suggestion
.Type(
externalId = Some(UUID.randomUUID()),
module = "Standard.Base.Data.Vector",
name = "Set",
params =
Seq.empty, // Set(Suggestion.Argument("foo", "bar", true, false, None, None)),
returnType = "Standard.Base.Data.Set.Set",
parentType = Some("Standard.Base.Any.Any"),
documentation =
Some(" An unordered collection of unique values"),
reexports = Set("foo")
),
action = SuggestionAction.Modify(documentation = Some(None))
),
children = Vector.empty
)
)
)
)
)
)
)

val d1 = ApiSerializer.serialize(message)
val d2 = ApiSerializer.deserializeApiEnvelope(d1).get

message should equal(d2)

val libLoaded =
Api.Response(
None,
Api.LibraryLoaded(
"Standard",
"Base",
"0.0.0-dev",
new java.io.File(
"enso/built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/lib/Standard/Base/0.0.0-dev"
)
)
)
val e1 = ApiSerializer.serialize(libLoaded)
val e2 = ApiSerializer.deserializeApiEnvelope(e1).get

libLoaded should equal(e2)
}

}