All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avsystem.commons.redis.RedisDataCodec.scala Maven / Gradle / Ivy

package com.avsystem.commons
package redis

import akka.util.ByteString
import com.avsystem.commons.redis.protocol.BulkStringMsg
import com.avsystem.commons.serialization.GenCodec.ReadFailure
import com.avsystem.commons.serialization._
import com.avsystem.commons.serialization.json.{JsonReader, JsonStringInput, JsonStringOutput}

/**
  * Typeclass which expresses that values of some type are serializable to binary form (`ByteString`) and deserializable
  * from it in order to use them as keys, hash keys and values in Redis commands.
  *
  * By default, `RedisDataCodec` is provided for simple types like `String`, `ByteString`, `Array[Byte]`,
  * `Boolean`, `Char`, all primitive numeric types and `NamedEnum`s
  * (which have `NamedEnumCompanion`).
  *
  * Also, all types which have an instance of `GenCodec`
  * automatically have an instance of RedisDataCodec.
  */
case class RedisDataCodec[T](read: ByteString => T, write: T => ByteString)
object RedisDataCodec extends LowPriorityRedisDataCodecs {
  def apply[T](implicit rdc: RedisDataCodec[T]): RedisDataCodec[T] = rdc

  def write[T](value: T)(implicit rdc: RedisDataCodec[T]): ByteString = rdc.write(value)
  def read[T](raw: ByteString)(implicit rdc: RedisDataCodec[T]): T = rdc.read(raw)

  implicit val ByteStringCodec: RedisDataCodec[ByteString] =
    RedisDataCodec(identity, identity)
  implicit val ByteArrayCodec: RedisDataCodec[Array[Byte]] =
    RedisDataCodec(_.toArray, ByteString(_))
}
trait LowPriorityRedisDataCodecs { this: RedisDataCodec.type =>
  implicit def fromGenCodec[T: GenCodec]: RedisDataCodec[T] =
    RedisDataCodec(bytes => RedisDataInput.read(bytes), value => RedisDataOutput.write(value))
}

object RedisDataUtils {
  final val Null = ByteString(0)
}

object RedisDataOutput {
  def write[T: GenCodec](value: T): ByteString = {
    var bs: ByteString = null
    GenCodec.write(new RedisDataOutput(bs = _), value)
    bs
  }
}

final class RedisDataOutput(consumer: ByteString => Unit) extends OutputAndSimpleOutput {
  private def writeBytes(bytes: ByteString): Unit =
    if (bytes.headOpt.contains(0: Byte)) consumer(RedisDataUtils.Null ++ bytes)
    else consumer(bytes)

  def writeNull(): Unit = consumer(RedisDataUtils.Null)
  def writeBoolean(boolean: Boolean): Unit = writeInt(if (boolean) 1 else 0)
  def writeString(str: String): Unit = writeBytes(ByteString(str))
  def writeInt(int: Int): Unit = writeString(int.toString)
  def writeLong(long: Long): Unit = writeString(long.toString)
  def writeDouble(double: Double): Unit = writeString(double.toString)
  def writeBigInt(bigInt: BigInt): Unit = writeString(bigInt.toString)
  def writeBigDecimal(bigDecimal: BigDecimal): Unit = writeString(bigDecimal.toString)
  def writeBinary(binary: Array[Byte]): Unit = writeBytes(ByteString(binary))

  def writeList(): ListOutput = new ListOutput {
    private val sb = new JStringBuilder
    private val jlo = new JsonStringOutput(sb).writeList()

    override def sizePolicy: SizePolicy = SizePolicy.Ignored
    def writeElement(): Output = jlo.writeElement()
    def finish(): Unit = {
      jlo.finish()
      consumer(ByteString(sb.toString))
    }
  }

  def writeObject(): ObjectOutput = new ObjectOutput {
    private val sb = new JStringBuilder
    private val joo = new JsonStringOutput(sb).writeObject()

    override def sizePolicy: SizePolicy = SizePolicy.Ignored
    def writeField(key: String): Output = joo.writeField(key)
    def finish(): Unit = {
      joo.finish()
      consumer(ByteString(sb.toString))
    }
  }
}

class RedisRecordOutput(builder: MBuilder[BulkStringMsg, _]) extends ObjectOutput {
  override def declareSize(size: Int): Unit =
    builder.sizeHint(size)

  def writeField(key: String): Output = {
    builder += BulkStringMsg(ByteString(key))
    new RedisDataOutput(bs => builder += BulkStringMsg(bs))
  }

  def finish(): Unit = ()
}

object RedisDataInput {
  def read[T: GenCodec](bytes: ByteString): T =
    GenCodec.read[T](new RedisDataInput(bytes))
}

class RedisDataInput(bytes: ByteString) extends InputAndSimpleInput {
  private lazy val jsonInput = new JsonStringInput(new JsonReader(readBytes().utf8String))

  private def fail(msg: String) = throw new ReadFailure(msg)
  private def readBytes(): ByteString = bytes match {
    case RedisDataUtils.Null => fail("null")
    case _ if bytes.headOpt.contains(0: Byte) => bytes.drop(1)
    case _ => bytes
  }

  def readNull(): Boolean = bytes == RedisDataUtils.Null
  def readString(): String = readBytes().utf8String
  def readBoolean(): Boolean = readString().toInt > 0
  def readInt(): Int = readString().toInt
  def readLong(): Long = readString().toLong
  def readDouble(): Double = readString().toDouble
  def readBigInt(): BigInt = BigInt(readString())
  def readBigDecimal(): BigDecimal = BigDecimal(readString())
  def readBinary(): Array[Byte] = readBytes().toArray

  def readList(): ListInput = jsonInput.readList()
  def readObject(): ObjectInput = jsonInput.readObject()

  def skip(): Unit = ()
}

class RedisFieldDataInput(val fieldName: String, bytes: ByteString)
  extends RedisDataInput(bytes) with FieldInput

class RedisRecordInput(bulks: IndexedSeq[BulkStringMsg]) extends ObjectInput {
  private val it = bulks.iterator.map(_.string)

  override def knownSize: Int = bulks.size

  def nextField(): FieldInput = {
    val fieldName = it.next().utf8String
    val bytes = it.next()
    new RedisFieldDataInput(fieldName, bytes)
  }

  def hasNext: Boolean = it.hasNext
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy