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

avokka.arangodb.protocol.ArangoClient.scala Maven / Gradle / Ivy

The newest version!
package avokka.arangodb
package protocol

import avokka.arangodb.types.DatabaseName
import avokka.velocypack._
import cats.MonadThrow
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.show._
import org.typelevel.log4cats.MessageLogger
import scodec.DecodeResult
import scodec.bits.ByteVector

/**
  * ArangoDB client
  *
  * @tparam F effect
  */
trait ArangoClient[F[_]] {

  /**
    * send a velocystream request message and receive a velocystream reply message
    *
    * @param message VST message
    * @return VST message
    */
  protected def send(message: ByteVector): F[ByteVector]

  /**
    * send a arangodb request without body and receive a arangodb response without body (HEAD)
    *
    * @param header arango request header
    * @return arango header
    */
  def execute(header: ArangoRequest.Header): F[ArangoResponse.Header]

  /**
    * send a arangodb request without body and receive arangodb response
    *
    * @param header arango header
    * @tparam O output body type
    * @return arango response
    */
  def execute[O: VPackDecoder](header: ArangoRequest.Header): F[ArangoResponse[O]]

  /**
    * send a arangodb request with a body payload and receive arangodb response
    *
    * @param request arango request
    * @tparam P payload body type
    * @tparam O output body type
    * @return arango response
    */
  def execute[P: VPackEncoder, O: VPackDecoder](request: ArangoRequest[P]): F[ArangoResponse[O]]

  /**
    * authenticate with arango server
    *
    * @param username
    * @param password
    * @return
    */
  def login(username: String, password: String): F[ArangoResponse[ArangoResponse.Error]]

  /** arangodb server api */
  def server: ArangoServer[F]

  /** database api */
  def database(name: DatabaseName): ArangoDatabase[F]

  /** _system database api */
  def system: ArangoDatabase[F]

  /** configured database api */
  def db: ArangoDatabase[F]

}

object ArangoClient {
  @inline def apply[F[_]](implicit ev: ArangoClient[F]): ArangoClient[F] = ev

  /**
    * Abstract implementation of client execute methods, send method is left to akka or fs2
    *
    * @param configuration client configuration
    * @param F monad
    * @param L logger
    * @tparam F effect
    */
  abstract class Impl[F[_]](configuration: ArangoConfiguration)(implicit F: MonadThrow[F], L: MessageLogger[F])
    extends ArangoClient[F] {

    override val server: ArangoServer[F] = ArangoServer(this, F)

    override def database(name: DatabaseName): ArangoDatabase[F] = ArangoDatabase(name)(this, F)
    override val system: ArangoDatabase[F] = database(DatabaseName.system)
    override val db: ArangoDatabase[F] = database(configuration.database)

    override def login(username: String, password: String): F[ArangoResponse[ArangoResponse.Error]] = execute(
      ArangoRequest.Authentication(user = username, password = password)
    )

    private val traceREQ = Console.CYAN + "\u25b2REQ" + Console.RESET
    private val traceRES = Console.CYAN_B + Console.BLACK + "\u25bcRES" + Console.RESET

    override def execute(header: ArangoRequest.Header): F[ArangoResponse.Header] = for {
      _      <- L.trace(show"$traceREQ header $header")
      in     <- F.fromEither(header.toVPackBits)
      _      <- L.trace(show"$traceREQ header ${in.asVPackValue}")
      out    <- send(in.bytes)
      _      <- L.trace(show"$traceRES header ${out.bits.asVPackValue}")
      header <- F.fromEither(out.bits.asVPack[ArangoResponse.Header])
      _      <- L.trace(show"$traceRES header ${header.value}")
      res    <- if (header.value.responseCode >= 300) {
        F.raiseError(ArangoError.Header(header.value))
      } else {
        F.pure(header.value)
      }
    } yield res

    override def execute[O: VPackDecoder](header: ArangoRequest.Header): F[ArangoResponse[O]] =
      for {
        _  <- L.trace(show"$traceREQ header $header")
        in <- F.fromEither(header.toVPackBits)
        _  <- L.trace(show"$traceREQ header ${in.asVPackValue}")
        out <- send(in.bytes)
        res <- handleResponse(out)
      } yield res

    override def execute[P: VPackEncoder, O: VPackDecoder](request: ArangoRequest[P]): F[ArangoResponse[O]] =
      for {
        _   <- L.trace(show"$traceREQ header ${request.header}")
        inh <- F.fromEither(request.header.toVPackBits)
        _   <- L.trace(show"$traceREQ header ${inh.asVPackValue}")
        inb <- F.fromEither(request.body.toVPackBits)
        _   <- L.trace(show"$traceREQ body ${inb.asVPackValue}")
        out <- send((inh ++ inb).bytes)
        res <- handleResponse(out)
      } yield res

    protected def handleResponse[O: VPackDecoder](response: ByteVector): F[ArangoResponse[O]] =
      for {
        _      <- L.trace(show"$traceRES header ${response.bits.asVPackValue}")
        header <- F.fromEither(response.bits.asVPack[ArangoResponse.Header])
        _      <- L.trace(show"$traceRES header ${header.value}")
        body <- if (header.remainder.isEmpty) {
          F.raiseError[DecodeResult[O]](ArangoError.Header(header.value))
        } else if (header.value.responseCode >= 300) {
          L.trace(show"$traceRES body ${header.remainder.asVPackValue}") >>
          F.fromEither(header.remainder.asVPack[ArangoResponse.Error]) >>=
            (err => F.raiseError[DecodeResult[O]](ArangoError.Response(header.value, err.value)))
        } else {
          L.trace(show"$traceRES body ${header.remainder.asVPackValue}") >>
          F.fromEither(header.remainder.asVPack[O])
        }
      } yield ArangoResponse(header.value, body.value)

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy