6

I am looking for a way to approach a protocol like the following example:

case class Request(bodyType: Int, foo: Int, bar: Int, body: RequestBody)

sealed trait RequestBody
case class Read(key: String) extends RequestBody
case class Write(key: String, value: Array[Byte]) extends RequestBody 

Here, bodyType == 0 will stand for Read, and bodyType != 0 will encode Write. Note that there are a few fields separating discriminator from discriminated value.

I've seen an example with byte-ordering. But as far as I understand this "squid" encoded discriminator would not round trip. What's the correct way to solve such a problem?

ziggystar
  • 28,410
  • 9
  • 72
  • 124
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228

1 Answers1

5

There are a few ways to do it, but this is one I've used:

import scodec._
import scodec.codecs._
import scodec.bits._

case class Request(bodyType: Int, foo: Int, bar: Int, body: RequestBody)

sealed trait RequestBody
case class Read(key: String) extends RequestBody
object Read {
  implicit val codec: Codec[Read] = ("key" | utf8).as[Read]
  implicit val discriminator: Discriminator[RequestBody, Read, Int] = Discriminator(0)
}
case class Write(key: String, value: ByteVector) extends RequestBody
object Write {
  implicit val codec: Codec[Write] = {
    ("key"   | utf8  ) ::
    ("value" | bytes )
  }.as[Write]
  implicit val discriminator: Discriminator[RequestBody, Write, Int] = Discriminator(1)
}

object Request {
  implicit val codec: Codec[Request] = {
    ("bodyType" | uint16 ).flatPrepend { bodyType =>
    ("foo"      | uint16 ) ::
    ("bar"      | uint16 ) ::
    ("body"     | Codec.coproduct[RequestBody].discriminatedBy(provide(bodyType)).auto)
  }}.as[Request]
} 
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • This works great, thanks! I have some issues with `ByteVector` roundtrip, but overall works like a charm – om-nom-nom Jan 10 '16 at 21:49
  • Turns out I had this problem because of `utf8`, which redirects to `string` and [one cannot simply use `string` if it's not the very last field](https://github.com/scodec/scodec/blob/master/shared/src/main/scala/scodec/codecs/package.scala#L488-L493) – om-nom-nom Jan 10 '16 at 21:56
  • 1
    sorry, i should have constrained it with fixedSizeBytes(5, utf8) (or whatever the size of your string is) otherwise it'll keep consuming bytes until it hits a non-utf8 encoded byte or the input bits are exhausted as you indicate). But glad it worked for you. – Steve Buzzard Jan 11 '16 at 03:10