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

camundala.camunda7.worker.RestApiClient.scala Maven / Gradle / Ivy

There is a newer version: 1.30.23
Show newest version
package camundala
package camunda7.worker

import camundala.bpmn.*
import camundala.domain.*
import camundala.worker.*
import camundala.worker.CamundalaWorkerError.*
import io.circe.parser
import sttp.client3.*
import sttp.client3.circe.*
import sttp.model.Uri.QuerySegment
import sttp.model.{Header, Uri}

import scala.util.Try
import scala.reflect.ClassTag

trait RestApiClient:

  def sendRequest[
      ServiceIn: InOutEncoder, // body of service
      ServiceOut: InOutDecoder: ClassTag // output of service
  ](
      runnableRequest: RunnableRequest[ServiceIn]
  ): SendRequestType[ServiceOut] =
    try
      for
        reqWithOptBody <- requestWithOptBody(runnableRequest)
        req <- auth(reqWithOptBody)
        response <- sendRequest(req)
        statusCode = response.code
        body <- readBody(statusCode, response, req)
        headers = response.headers.map(h => h.name -> h.value).toMap
        out <- decodeResponse[ServiceOut](body)
      yield ServiceResponse(out, headers)
    catch
      case ex: Throwable =>
        val unexpectedError =
          s"""Unexpected error while sending request: ${ex.getMessage}.
             | -> $runnableRequest
             |""".stripMargin
        ex.printStackTrace()
        Left(ServiceUnexpectedError(unexpectedError))
  end sendRequest

  protected def readBody(
      statusCode: StatusCode,
      response: Response[Either[String, String]],
      request: Request[Either[String, String], Any]
  ): Either[ServiceRequestError, String] =
    response.body.left
      .map(body =>
        ServiceRequestError(
          statusCode.code,
          s"Non-2xx response with code $statusCode:\n$body\n\n${request.toCurl}"
        )
      )
  end readBody

  // no auth per default
  protected def auth(
      request: Request[Either[String, String], Any]
  )(using EngineRunContext): Either[ServiceAuthError, Request[Either[String, String], Any]] =
    Right(request)

  protected def sendRequest(
      request: Request[Either[String, String], Any]
  ) =
    try
      Right(request.send(backend))
    catch
      case ex: Throwable =>
        val unexpectedError =
          s"""Unexpected error while sending request: ${ex.getMessage}.
             | -> ${request.toCurl(Set("Authorization"))}
             |""".stripMargin
        ex.printStackTrace()
        Left(ServiceUnexpectedError(unexpectedError))

  protected def decodeResponse[
      ServiceOut: InOutDecoder: ClassTag // output of service
  ](
      body: String
  ): Either[ServiceBadBodyError, ServiceOut] =
    if hasNoOutput[ServiceOut]() 
    then  Right(NoOutput().asInstanceOf[ServiceOut])
    else
      if  body.isBlank then
        val runtimeClass = implicitly[ClassTag[ServiceOut]].runtimeClass
        runtimeClass match
          case x if x == classOf[Option[?]] =>
            Right(None.asInstanceOf[ServiceOut])
          case other =>
            Left(ServiceBadBodyError(
              s"There is no body in the response and the ServiceOut is neither NoOutput nor Option (Class is $other)."
            ))
        end match
      else
        parser
          .decodeAccumulating[ServiceOut](body)
          .toEither
          .left
          .map(err => ServiceBadBodyError(s"Problem creating body from response.\n$err\nBODY: $body"))

  protected def requestWithOptBody[ServiceIn: InOutEncoder](
      runnableRequest: RunnableRequest[ServiceIn]
  ): Either[ServiceBadBodyError, RequestT[Identity, Either[String, String], Any]] =
    val request =
      requestMethod(
        runnableRequest.httpMethod,
        runnableRequest.apiUri,
        runnableRequest.qSegments,
        runnableRequest.headers
      )
    Try(runnableRequest.requestBodyOpt.map(b =>
      request.body(b.asJson.deepDropNullValues)
    ).getOrElse(request)).toEither.left
      .map(err => ServiceBadBodyError(errorMsg = s"Problem creating body for request.\n$err"))
  end requestWithOptBody

  private def requestMethod(
      httpMethod: Method,
      apiUri: Uri,
      qSegments: Seq[QuerySegment],
      headers: Map[String, String]
  ): Request[Either[String, String], Any] =
    basicRequest
      .copy(
        uri = apiUri.addQuerySegments(qSegments),
        headers = headers.toSeq.map { case k -> v => Header(k, v) },
        method = httpMethod
      )
  end requestMethod

  private[worker] def hasNoOutput[ServiceOut: ClassTag](): Boolean =
    val runtimeClass = implicitly[ClassTag[ServiceOut]].runtimeClass
    runtimeClass == classOf[NoOutput]

end RestApiClient

object DefaultRestApiClient extends RestApiClient




© 2015 - 2024 Weber Informatics LLC | Privacy Policy