Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions core/src/jsonrpclib/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@ object Codec {
def decode[A](payload: Option[Payload])(implicit codec: Codec[A]): Either[ProtocolError, A] = codec.decode(payload)

implicit def fromJsonCodec[A](implicit jsonCodec: JsonValueCodec[A]): Codec[A] = new Codec[A] {
def encode(a: A): Payload = Payload(writeToArray(a))
def encode(a: A): Payload = {
Payload(writeToArray(a))
}

def decode(payload: Option[Payload]): Either[ProtocolError, A] = {
try {
payload match {
case Some(Payload(array)) => Right(readFromArray(array))
case None => Left(ProtocolError.ParseError("Expected to decode a payload"))
case Some(Payload.Data(payload)) => Right(readFromArray(payload))
case Some(Payload.NullPayload) => Right(readFromArray(nullArray))
case None => Left(ProtocolError.ParseError("Expected to decode a payload"))
}
} catch { case e: JsonReaderException => Left(ProtocolError.ParseError(e.getMessage())) }
}
}

private val nullArray = "null".getBytes()

}
39 changes: 29 additions & 10 deletions core/src/jsonrpclib/Payload.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,45 @@ import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
import com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter

import java.util.Base64
import jsonrpclib.Payload.Data
import jsonrpclib.Payload.NullPayload

final case class Payload(array: Array[Byte]) {
override def equals(other: Any) = other match {
case bytes: Payload => java.util.Arrays.equals(array, bytes.array)
case _ => false
sealed trait Payload extends Product with Serializable {
def stripNull: Option[Payload.Data] = this match {
case d @ Data(_) => Some(d)
case NullPayload => None
}

override lazy val hashCode: Int = java.util.Arrays.hashCode(array)

override def toString = Base64.getEncoder.encodeToString(array)
}

object Payload {
def apply(value: Array[Byte]) = {
if (value == null) NullPayload
else Data(value)
}
final case class Data(array: Array[Byte]) extends Payload {
override def equals(other: Any) = other match {
case bytes: Data => java.util.Arrays.equals(array, bytes.array)
case _ => false
}

override lazy val hashCode: Int = java.util.Arrays.hashCode(array)

override def toString = Base64.getEncoder.encodeToString(array)
}

case object NullPayload extends Payload

implicit val payloadJsonValueCodec: JsonValueCodec[Payload] = new JsonValueCodec[Payload] {
def decodeValue(in: JsonReader, default: Payload): Payload = {
Payload(in.readRawValAsBytes())
Data(in.readRawValAsBytes())
}

def encodeValue(bytes: Payload, out: JsonWriter): Unit =
out.writeRawVal(bytes.array)
bytes match {
case Data(array) => out.writeRawVal(array)
case NullPayload => out.writeNull()

}

def nullValue: Payload = null
}
Expand Down
10 changes: 6 additions & 4 deletions core/src/jsonrpclib/internals/RawMessage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package internals

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

private[jsonrpclib] case class RawMessage(
jsonrpc: String,
method: Option[String] = None,
result: Option[Payload] = None,
result: Option[Option[Payload]] = None,
error: Option[ErrorPayload] = None,
params: Option[Payload] = None,
id: Option[CallId] = None
Expand All @@ -21,7 +22,7 @@ private[jsonrpclib] case class RawMessage(
case (Some(callId), None) =>
(error, result) match {
case (Some(error), _) => Right(OutputMessage.ErrorMessage(callId, error))
case (_, Some(data)) => Right(OutputMessage.ResponseMessage(callId, data))
case (_, Some(data)) => Right(OutputMessage.ResponseMessage(callId, data.getOrElse(Payload.NullPayload)))
case (None, None) =>
Left(
ProtocolError.InvalidRequest(
Expand All @@ -48,10 +49,11 @@ private[jsonrpclib] object RawMessage {
RawMessage(`2.0`, method = Some(method), params = params, id = Some(callId))
case OutputMessage.ErrorMessage(callId, errorPayload) =>
RawMessage(`2.0`, error = Some(errorPayload), id = Some(callId))
case OutputMessage.ResponseMessage(callId, data) => RawMessage(`2.0`, result = Some(data), id = Some(callId))
case OutputMessage.ResponseMessage(callId, data) =>
RawMessage(`2.0`, result = Some(data.stripNull), id = Some(callId))
}

implicit val rawMessageJsonValueCodecs: JsonValueCodec[RawMessage] =
JsonCodecMaker.make
JsonCodecMaker.make(CodecMakerConfig.withSkipNestedOptionValues(true))

}
30 changes: 30 additions & 0 deletions core/test/src-jvm-native/jsonrpclib/RawMessageSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package jsonrpclib

import munit._
import com.github.plokhotnyuk.jsoniter_scala.core._
import internals._
import jsonrpclib.CallId.NumberId
import jsonrpclib.OutputMessage.ResponseMessage

class RawMessageSpec() extends FunSuite {
test("json parsing with null result") {
// This is a perfectly valid response object, as result field has to be present,
// but can be null: https://www.jsonrpc.org/specification#response_object
val rawMessage = readFromString[RawMessage](""" {"jsonrpc":"2.0","result":null,"id":3} """.trim)
assertEquals(
rawMessage,
RawMessage(jsonrpc = "2.0", result = Some(None), id = Some(NumberId(3)))
)

assertEquals(rawMessage.toMessage, Right(ResponseMessage(NumberId(3), Payload.NullPayload)))

// This, on the other hand, is an invalid response message, as result field is missing
val invalidRawMessage = readFromString[RawMessage](""" {"jsonrpc":"2.0","id":3} """.trim)
assertEquals(
invalidRawMessage,
RawMessage(jsonrpc = "2.0", result = None, id = Some(NumberId(3)))
)

assert(invalidRawMessage.toMessage.isLeft, invalidRawMessage.toMessage)
}
}
12 changes: 9 additions & 3 deletions fs2/src/jsonrpclib/fs2/lsp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import jsonrpclib.Message
import jsonrpclib.ProtocolError
import jsonrpclib.Payload.Data
import jsonrpclib.Payload.NullPayload

object lsp {

Expand Down Expand Up @@ -42,11 +44,15 @@ object lsp {
}

private def writeChunk(payload: Payload): Chunk[Byte] = {
val size = payload.array.size
val header = s"Content-Length: ${size}" + "\r\n" * 2
Chunk.array(header.getBytes()) ++ Chunk.array(payload.array)
val bytes = payload match {
case Data(array) => array
case NullPayload => nullArray
}
val header = s"Content-Length: ${bytes.size}" + "\r\n" * 2
Chunk.array(header.getBytes()) ++ Chunk.array(bytes)
}

private val nullArray = "null".getBytes()
private val returnByte = '\r'.toByte
private val newlineByte = '\n'.toByte

Expand Down