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

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

package com.avsystem.commons
package redis

import akka.util.ByteString
import com.avsystem.commons.redis.ApiSubset.{HeadOps, IterableTailOps, IteratorTailOps}
import com.avsystem.commons.redis.commands._
import com.avsystem.commons.redis.config.ExecutionConfig
import com.avsystem.commons.redis.util.{HeadIterable, HeadIterator, SingletonSeq}
import monix.eval.Task

import scala.concurrent.Await
import scala.concurrent.duration._

/**
  * Encapsulates types that Redis commands may be parameterized with and provides serialization for these types.
  */
trait RedisSerialization {
  /**
    * The type of Redis keys or key patterns used in methods representing Redis commands. For example, if `Key = String`
    * then [[commands.StringsApi.get get]] returns `Result[Opt[String]]`. This type is used only for toplevel Redis keys,
    * hash keys have their own type, [[Field]]
    */
  type Key

  /**
    * The type of Redis hash keys or hash key patterns used in methods representing Redis commands that work on
    * hashes ([[commands.HashesApi HashesApi]]).
    */
  type Field

  /**
    * The type of Redis values used in methods representing Redis commands. "Value" is the data that might be
    * stored directly under a Redis key (e.g. using [[commands.StringsApi.set set]]) but also a value of hash field,
    * list element, set member, sorted set member, geo set member or element inserted into hyper-log-log structure.
    * There are no separate types specified for every of those use cases because only one of them can be used in a
    * single command (for example, there is no Redis command that works on both list elements and set members
    * at the same time).
    */
  type Value

  /**
    * Type used to represent key-value pair sequences, primarily entries in [[commands.StreamsApi StreamsApi]]
    * but also hashes in [[commands.HashesApi HashesApi]]. `Record` might be a "raw" type like `Map[Key, Value]` or
    * `Seq[(Key, Value)]` but normally `Record` is customized to be an ADT - a case class or sealed hierarchy.
    */
  type Record

  def keyCodec: RedisDataCodec[Key]
  def fieldCodec: RedisDataCodec[Field]
  def valueCodec: RedisDataCodec[Value]
  def recordCodec: RedisRecordCodec[Record]

  type WithKey[K] = RedisSerialization.Aux[K, Field, Value, Record]
  type WithField[F] = RedisSerialization.Aux[Key, F, Value, Record]
  type WithValue[V] = RedisSerialization.Aux[Key, Field, V, Record]
  type WithRecord[R] = RedisSerialization.Aux[Key, Field, Value, R]

  def keyType[K0: RedisDataCodec]: WithKey[K0]
  def fieldType[F0: RedisDataCodec]: WithField[F0]
  def valueType[V0: RedisDataCodec]: WithValue[V0]
  def recordType[R0: RedisRecordCodec]: WithRecord[R0]
}

object RedisSerialization {
  type Aux[K, F, V, R] = RedisSerialization {
    type Key = K
    type Field = F
    type Value = V
    type Record = R
  }

  object Strings extends Generic[String, String, String, Map[String, String]]
  object ByteStrings extends Generic[ByteString, ByteString, ByteString, Map[ByteString, ByteString]]

  class Generic[K, F, V, R](implicit
    val keyCodec: RedisDataCodec[K],
    val fieldCodec: RedisDataCodec[F],
    val valueCodec: RedisDataCodec[V],
    val recordCodec: RedisRecordCodec[R]
  ) extends RedisSerialization {
    type Key = K
    type Field = F
    type Value = V
    type Record = R

    def keyType[K0: RedisDataCodec]: WithKey[K0] = new Generic
    def fieldType[F0: RedisDataCodec]: WithField[F0] = new Generic
    def valueType[V0: RedisDataCodec]: WithValue[V0] = new Generic
    def recordType[R0: RedisRecordCodec]: WithRecord[R0] = new Generic
  }
}

trait ApiSubset { self =>
  val serialization: RedisSerialization

  type Key = serialization.Key
  type Field = serialization.Field
  type Value = serialization.Value
  type Record = serialization.Record

  protected implicit final def keyCodec: RedisDataCodec[Key] = serialization.keyCodec
  protected implicit final def fieldCodec: RedisDataCodec[Field] = serialization.fieldCodec
  protected implicit final def valueCodec: RedisDataCodec[Value] = serialization.valueCodec
  protected implicit final def recordCodec: RedisRecordCodec[Record] = serialization.recordCodec

  /**
    * The type constructor into which a result of each command is wrapped. For example if `Result` is
    * `Future`, then [[commands.StringsApi.incr incr]] returns `Future[Long]`.
    */
  type Result[A]

  def execute[A](command: RedisCommand[A]): Result[A]

  protected implicit def iterableTailOps[T](tail: Iterable[T]): IterableTailOps[T] = new IterableTailOps(tail)
  protected implicit def iteratorTailOps[T](tail: Iterator[T]): IteratorTailOps[T] = new IteratorTailOps(tail)
  protected implicit def headOps[T](head: T): HeadOps[T] = new HeadOps(head)
}

object ApiSubset {
  class HeadOps[A](private val head: A) extends AnyVal {
    def single: Seq[A] = new SingletonSeq[A](head)
  }
  class IterableTailOps[A](private val tail: Iterable[A]) extends AnyVal {
    def +::(head: A): Iterable[A] = new HeadIterable(head, tail)
  }
  class IteratorTailOps[A](private val tail: Iterator[A]) extends AnyVal {
    def +::(head: A): Iterator[A] = new HeadIterator(head, tail)
  }
}

trait RecoverableApiSubset extends ApiSubset {
  protected def recoverWith[A](executed: => Result[A])(fun: PartialFunction[Throwable, Result[A]]): Result[A]
}

trait RedisRawApi extends ApiSubset {
  type Result[A] = RawCommand
  def execute[A](command: RedisCommand[A]): RedisCommand[A] = command
}

trait RedisBatchApi extends ApiSubset {
  type Result[A] = RedisBatch[A]
  def execute[A](command: RedisCommand[A]): RedisBatch[A] = command.batchOrFallback
}

trait RedisExecutedApi extends RecoverableApiSubset {
  def execConfig: ExecutionConfig
  def executor: RedisExecutor
  def executeAsync[A](command: RedisCommand[A]): Future[A] =
    executor.executeBatch(command.batchOrFallback, execConfig)
}

trait RedisAsyncApi extends RedisExecutedApi {
  type Result[A] = Future[A]
  def execute[A](command: RedisCommand[A]): Future[A] = executeAsync(command)
  def recoverWith[A](executed: => Future[A])(fun: PartialFunction[Throwable, Future[A]]): Future[A] =
    executed.recoverWith(fun)(executor.executionContext)
}

trait RedisMonixApi extends RedisExecutedApi {
  type Result[A] = Task[A]

  def execute[A](command: RedisCommand[A]): Task[A] = Task.deferFutureAction { scheduler =>
    executor.executeBatch(command, execConfig.copy(decodeOn = scheduler))
  }

  def recoverWith[A](executed: => Task[A])(fun: PartialFunction[Throwable, Task[A]]): Task[A] =
    executed.onErrorRecoverWith(fun)
}

trait RedisBlockingApi extends RedisExecutedApi {
  type Result[A] = A
  def execute[A](command: RedisCommand[A]): A =
  // executeAsync should already handle timeouts, but just to be safe let's pass the standard timeout plus one second
    Await.result(executeAsync(command), execConfig.responseTimeout.duration + 1.second)
  def recoverWith[A](executed: => A)(fun: PartialFunction[Throwable, A]): A =
    try executed catch fun
}

trait RedisKeyedApi extends AnyRef
  with KeyedKeysApi
  with StringsApi
  with KeyedClusterApi
  with GeoApi
  with KeyedScriptingApi
  with HashesApi
  with SortedSetsApi
  with ListsApi
  with SetsApi
  with HyperLogLogApi
  with StreamsApi

trait RedisRecoverableKeyedApi extends RedisKeyedApi
  with RecoverableKeyedScriptingApi

trait RedisNodeApi extends RedisKeyedApi
  with NodeKeysApi
  with NodeServerApi
  with NodeClusterApi
  with NodeConnectionApi
  with NodeScriptingApi
  with SentinelApi

trait RedisRecoverableNodeApi extends RedisRecoverableKeyedApi with RedisNodeApi

trait RedisOperationApi extends RedisNodeApi
  with TransactionApi

trait RedisConnectionApi extends RedisOperationApi
  with ConnectionClusterApi
  with ConnectionConnectionApi
  with ConnectionServerApi
  with ConnectionScriptingApi

trait RedisRecoverableConnectionApi extends RedisRecoverableNodeApi with RedisConnectionApi




© 2015 - 2025 Weber Informatics LLC | Privacy Policy