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

io.chrisdavenport.rediculous.RedisResult.scala Maven / Gradle / Ivy

The newest version!
package io.chrisdavenport.rediculous

import cats._
import cats.syntax.all._
import scodec.bits.ByteVector

trait RedisResult[+A]{
  def decode(resp: Resp): Either[Resp, A]
}
object RedisResult extends RedisResultLowPriority{
  def apply[A](implicit ev: RedisResult[A]): ev.type = ev

  implicit val functor: Functor[RedisResult] = new Functor[RedisResult]{
    def map[A, B](fa: RedisResult[A])(f: A => B): RedisResult[B] = new RedisResult[B] {
      def decode(resp: Resp): Either[Resp,B] = fa.decode(resp).map(f)
    }
  }
  
  implicit val resp: RedisResult[Resp] = new RedisResult[Resp]{
    def decode(resp: Resp): Either[Resp,Resp] = Right(resp)
  }

  implicit val string : RedisResult[String] = new RedisResult[String]{
    def decode(resp: Resp): Either[Resp,String] = resp match {
      case Resp.SimpleString(value) => value.asRight
      case Resp.BulkString(Some(value)) => value.decodeUtf8.leftMap(_ => resp)
      case otherwise => otherwise.asLeft
    }
  }

  implicit val bytevector: RedisResult[ByteVector] = new RedisResult[ByteVector] {
    def decode(resp: Resp): Either[Resp,ByteVector] = resp match {
      case Resp.BulkString(Some(value)) => value.asRight
      case otherwise => otherwise.asLeft
    }
  }

  implicit def option[A: RedisResult]: RedisResult[Option[A]] = new RedisResult[Option[A]] {
    def decode(resp: Resp): Either[Resp,Option[A]] = resp match {
      case Resp.BulkString(None) => None.asRight
      case Resp.Array(None) => None.asRight
      case otherwise => RedisResult[A].decode(otherwise).map(_.some)
    }
  }

  implicit val status: RedisResult[RedisProtocol.Status] = new RedisResult[RedisProtocol.Status] {
    def decode(resp: Resp): Either[Resp,RedisProtocol.Status] = resp match {
      case Resp.SimpleString(value) => Either.right(value match {
        case "OK" => RedisProtocol.Status.Ok
        case "PONG" => RedisProtocol.Status.Pong
        case otherwise => RedisProtocol.Status.Status(otherwise)
      })
      case otherwise => Left(otherwise)
    }
  }

  implicit val redisType: RedisResult[RedisProtocol.RedisType] = new RedisResult[RedisProtocol.RedisType] {
    def decode(resp: Resp): Either[Resp,RedisProtocol.RedisType] = resp match {
      case Resp.SimpleString(value) => Either.right(value match {
        case "none" => RedisProtocol.RedisType.None
        case "string" => RedisProtocol.RedisType.String
        case "hash" => RedisProtocol.RedisType.Hash
        case "list" => RedisProtocol.RedisType.List
        case "set" => RedisProtocol.RedisType.Set
        case "stream" => RedisProtocol.RedisType.Stream
        case "zset" => RedisProtocol.RedisType.ZSet
        case _ => throw RedisError.Generic(s"Rediculous: Unhandled red type: $value")
      })
      case r => Left(r)
    }
  }
  
  implicit val bool: RedisResult[Boolean] = new RedisResult[Boolean] {
    def decode(resp: Resp): Either[Resp,Boolean] = resp match {
      case Resp.Integer(1) => Right(true)
      case Resp.Integer(0) => Right(false)
      case Resp.BulkString(None) => Right(false) // Lua boolean false
      case r => Left(r)
    }
  }

  implicit val long: RedisResult[Long] = new RedisResult[Long] {
    def decode(resp: Resp): Either[Resp,Long] = resp match {
      case Resp.Integer(l) => Right(l)
      case other => RedisResult[String].decode(other)
        .flatMap(s => Either.catchNonFatal(s.toLong).leftMap(_ => resp))
    }
  }

  // Increment
  implicit val double: RedisResult[Double] = new RedisResult[Double] {
    def decode(resp: Resp): Either[Resp,Double] = RedisResult[String].decode(resp)
      .flatMap(s => Either.catchNonFatal(s.toDouble).leftMap(_ => resp))
  }

  implicit val int: RedisResult[Int] = long.map(_.toInt) // Integers are longs in redis, use at your own risk.

  implicit def tuple[A: RedisResult, B: RedisResult]: RedisResult[(A, B)] = new RedisResult[(A, B)] {
    def decode(resp: Resp): Either[Resp,(A, B)] = resp match {
      case Resp.Array(Some(x ::y :: Nil)) => (RedisResult[A].decode(x), RedisResult[B].decode(y)).tupled
      case otherwise => Left(otherwise)
    }
  }

  implicit def tuple3[A: RedisResult, B: RedisResult, C: RedisResult]: RedisResult[(A, B, C)] = new RedisResult[(A, B, C)] {
    def decode(resp: Resp): Either[Resp,(A, B, C)] = resp match {
      case Resp.Array(Some(x ::y :: z :: Nil)) => (RedisResult[A].decode(x), RedisResult[B].decode(y), RedisResult[C].decode(z)).tupled
      case otherwise => Left(otherwise)
    }
  }

  implicit def tuple4[A: RedisResult, B: RedisResult, C: RedisResult, D: RedisResult]: RedisResult[(A, B, C, D)] = new RedisResult[(A, B, C, D)] {
    def decode(resp: Resp): Either[Resp,(A, B, C, D)] = resp match {
      case Resp.Array(Some(a :: b :: c :: d :: Nil)) => 
        (
          RedisResult[A].decode(a), 
          RedisResult[B].decode(b),
          RedisResult[C].decode(c),
          RedisResult[D].decode(d),
        ).tupled
      case otherwise => Left(otherwise)
    }
  }

  implicit def kv[K: RedisResult, V: RedisResult]: RedisResult[List[(K, V)]] = 
    new RedisResult[List[(K, V)]] {
      def decode(resp: Resp): Either[Resp,List[(K, V)]] = {

        def pairs(l: List[Resp]): Either[Resp,List[(K, V)]] =
          Monad[Either[Resp, *]].tailRecM[(List[Resp], List[(K, V)]), List[(K, V)]]((l, Nil)){
            case (l, acc) =>
              l match {
                case Nil => Right(Right(acc))
                case _ :: Nil => Left(resp)
                case x1 :: x2 :: xs => for {
                  k <- RedisResult[K].decode(x1)
                  v <- RedisResult[V].decode(x2)
                } yield Left((xs, (k, v) :: acc))
              }
        }.map(_.reverse)

        resp match {
          case Resp.Array(Some(rs)) => pairs(rs)
          case otherwise => Left(otherwise)
        }
      }
    }

}

private[rediculous] trait RedisResultLowPriority {

  implicit def list[A: RedisResult]: RedisResult[List[A]] = new RedisResult[List[A]] {
    def decode(resp: Resp): Either[Resp,List[A]] = resp match {
      case Resp.Array(Some(x)) => x.traverse(RedisResult[A].decode)
      case other => Left(other)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy