refactor: Refactor response handler

**Problem**
The sbtn response handling code is relatively stragightforward,
but it's a bit messy.

**Solution**
This cleans it up a bit, similar to the style used by Unfiltered
back then (not sure how Unfiltered plans are written nowadays) by
expressing each event handling as a partial function, and composing them
together using `orElse`.
This commit is contained in:
Eugene Yokota 2025-02-09 17:07:48 -05:00
parent 06acd261d5
commit a75e847a08
2 changed files with 69 additions and 46 deletions

View File

@ -80,6 +80,13 @@ object Util {
def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () })
}
/**
* Given a list of event handlers expressed partial functions, combine them
* together using orElse from the left.
*/
def reduceIntents[A1, A2](intents: PartialFunction[A1, A2]*): PartialFunction[A1, A2] =
intents.toList.reduceLeft(_ orElse _)
lazy val majorJavaVersion: Int =
try {
val javaVersion = sys.props.get("java.version").getOrElse("1.0")

View File

@ -529,61 +529,77 @@ class NetworkClient(
.getOrElse(1)
case _ => 1
}
private def completeExec(execId: String, exitCode: => Int): Unit =
private val onAttachResponse: PartialFunction[JsonRpcResponseMessage, Unit] = {
case msg if attachUUID.get == msg.id =>
attachUUID.set(null)
attached.set(true)
Option(inputThread.get).foreach(_.drain())
()
}
def completeExec(execId: String, exitCode: Int) = {
pendingResults.remove(execId) match {
case null =>
case null => ()
case (q, startTime, name) =>
val now = System.currentTimeMillis
val message = NetworkClient.timing(startTime, now)
val ec = exitCode
if (batchMode.get || !attached.get) {
if (ec == 0) console.success(message)
if (exitCode == 0) console.success(message)
else console.appendLog(Level.Error, message)
}
Util.ignoreResult(q.offer(ec))
}
def onResponse(msg: JsonRpcResponseMessage): Unit = {
completeExec(msg.id, getExitCode(msg.result))
pendingCancellations.remove(msg.id) match {
case null =>
case q => q.offer(msg.toString.contains("Task cancelled"))
}
msg.id match {
case execId =>
if (attachUUID.get == msg.id) {
attachUUID.set(null)
attached.set(true)
Option(inputThread.get).foreach(_.drain())
}
pendingCompletions.remove(execId) match {
case null =>
case completions =>
completions(msg.result match {
case Some(o: JObject) =>
o.value
.foldLeft(CompletionResponse(Vector.empty[String])) {
case (resp, i) =>
if (i.field == "items")
resp.withItems(
Converter
.fromJson[Vector[String]](i.value)
.getOrElse(Vector.empty[String])
)
else if (i.field == "cachedTestNames")
resp.withCachedTestNames(
Converter.fromJson[Boolean](i.value).getOrElse(true)
)
else if (i.field == "cachedMainClassNames")
resp.withCachedMainClassNames(
Converter.fromJson[Boolean](i.value).getOrElse(true)
)
else resp
}
case _ => CompletionResponse(Vector.empty[String])
})
}
Util.ignoreResult(q.offer(exitCode))
}
}
private val onExecResponse: PartialFunction[JsonRpcResponseMessage, Unit] = {
case msg if pendingResults.containsKey(msg.id) =>
completeExec(msg.id, getExitCode(msg.result))
}
private val onCancellationResponse: PartialFunction[JsonRpcResponseMessage, Unit] = {
case msg if pendingCancellations.containsKey(msg.id) =>
pendingCancellations.remove(msg.id) match {
case null => ()
case q => Util.ignoreResult(q.offer(msg.toString.contains("Task cancelled")))
}
}
private val onCompletionResponse: PartialFunction[JsonRpcResponseMessage, Unit] = {
case msg if pendingCompletions.containsKey(msg.id) =>
pendingCompletions.remove(msg.id) match {
case null => ()
case completions =>
completions(msg.result match {
case Some(o: JObject) =>
o.value
.foldLeft(CompletionResponse(Vector.empty[String])) {
case (resp, i) =>
if (i.field == "items")
resp.withItems(
Converter
.fromJson[Vector[String]](i.value)
.getOrElse(Vector.empty[String])
)
else if (i.field == "cachedTestNames")
resp.withCachedTestNames(
Converter.fromJson[Boolean](i.value).getOrElse(true)
)
else if (i.field == "cachedMainClassNames")
resp.withCachedMainClassNames(
Converter.fromJson[Boolean](i.value).getOrElse(true)
)
else resp
}
case _ => CompletionResponse(Vector.empty[String])
})
}
}
// cache the composed plan
private val responsePlan = Util.reduceIntents[JsonRpcResponseMessage, Unit](
onExecResponse,
onCancellationResponse,
onAttachResponse,
onCompletionResponse,
{ case _ => () },
)
def onResponse(msg: JsonRpcResponseMessage): Unit = responsePlan(msg)
def onNotification(msg: JsonRpcNotificationMessage): Unit = {
def splitToMessage: Vector[(Level.Value, String)] =