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

harness.http.client.ResponseOps.scala Maven / Gradle / Ivy

package harness.http.client

import cats.syntax.either.*
import harness.core.*
import harness.web.Constants.harnessInternalErrorHeader
import harness.web.ErrorCodec
import harness.zio.*
import java.util.Base64
import zio.*
import zio.json.*

object ResponseOps {

  trait Builder1[GetResponseR, ResponseT] extends Builder2[GetResponseR, ResponseT, Throwable] { self =>

    override protected val errorCodec: ErrorCodec[Throwable] = ErrorCodec.forThrowable

    final def withError[E: ErrorCodec]: Builder2[GetResponseR, ResponseT, E] =
      new Builder2[GetResponseR, ResponseT, E] {
        override protected def getResponse: RIO[GetResponseR & Scope, HttpResponse[ResponseT]] = self.getResponse
        override protected val errorCodec: ErrorCodec[E] = ErrorCodec[E]
      }

  }

  trait Builder2[GetResponseR, ResponseT, ErrorT] { self =>

    protected def getResponse: RIO[GetResponseR & Scope, HttpResponse[ResponseT]]
    protected val errorCodec: ErrorCodec[ErrorT]

    private def succeedOrDie[T](result: Either[String, T]): IO[Nothing, T] =
      result match {
        case Left(error)  => ZIO.dieMessage(s"Unable to decode response result:\n$error")
        case Right(value) => ZIO.succeed(value)
      }

    private def failOrDie(result: Either[String, ErrorT]): IO[ErrorT, Nothing] =
      result match {
        case Left(error)  => ZIO.dieMessage(s"Unable to decode error result:\n$error")
        case Right(value) => ZIO.fail(value)
      }

    private def attemptToDecode[T](f: String => Either[String, T]): ZIO[GetResponseR & Logger, ErrorT, T] =
      getResponseStringBody.orDie.flatMap { response =>
        ZIO.foreachDiscard(response.headers.getOrElse(harnessInternalErrorHeader, Nil)) { value =>
          Logger.log.debug(s"Harness internal error:\n${new String(Base64.getDecoder.decode(value))}")
        } *>
          (if (response.responseCode.is2xx)
             succeedOrDie(f(response.body))
           else if (response.responseCode.is4xxOr5xx)
             failOrDie(errorCodec.decode(response.body))
           else
             Logger.log.warning(s"Received non-(2xx/4xx/5xx) response: ${response.responseCode}") *>
               failOrDie(errorCodec.decode(response.body)))
      }

    // =====|  |=====

    final def getResponseStringBody: RIO[GetResponseR & Logger, HttpResponse[String]] =
      ZIO.scoped { self.getResponse.flatMap(_.toResponseStringBody) }

    final def stringBody: ZIO[GetResponseR & Logger, ErrorT, String] =
      attemptToDecode(_.asRight)

    final def encodedBody[T: StringDecoder]: ZIO[GetResponseR & Logger, ErrorT, T] =
      attemptToDecode(StringDecoder[T].decode)
    final def jsonBody[T: JsonDecoder]: ZIO[GetResponseR & Logger, ErrorT, T] =
      self.encodedBody[T](using StringDecoder.fromJsonDecoder[T])

    final def unit2xx: ZIO[GetResponseR & Logger, ErrorT, Unit] =
      attemptToDecode(_ => ().asRight)

    // =====|  |=====

    final def forwardBodyToPath(path: Path): RIO[GetResponseR, Long] =
      ZIO.scoped {
        self.getResponse.flatMap { response => response.result.ops.forwardBodyToPath(path, response.body) }
      }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy