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

camundala.worker.Handler.scala Maven / Gradle / Ivy

The newest version!
package camundala
package worker

import camundala.domain.*
import camundala.bpmn.*
import camundala.worker.CamundalaWorkerError.*
import io.circe
import sttp.model.Uri.QuerySegment
import sttp.model.{Method, Uri}
import scala.reflect.ClassTag

/** handler for Custom Validation (next to the automatic Validation of the In Object.
  *
  * For example if one of two optional variables must exist.
  *
  * Usage:
  * ```
  *  .withValidation(
  *    ValidationHandler(
  *      (in: In) => Right(in)
  *    )
  *  )
  * ```
  * or (with implicit conversion)
  * ```
  *  .withValidation(
  *      (in: In) => Right(in)
  *  )
  * ```
  * Default is no extra Validation.
  */
trait ValidationHandler[In <: Product: circe.Codec]:
  def validate(in: In): Either[ValidatorError, In]
end ValidationHandler

object ValidationHandler:
  def apply[
      In <: Product: InOutCodec
  ](funct: In => Either[ValidatorError, In]): ValidationHandler[In] =
    new ValidationHandler[In]:
      override def validate(in: In): Either[ValidatorError, In] =
        funct(in)
end ValidationHandler

/** handler for Custom Process Initialisation. All the variables in the Result Map will be put on
  * the process.
  *
  * For example if you want to init process Variables to a certain value.
  *
  * Usage:
  * ```
  *  .withValidation(
  *    InitProcessHandler(
  *      (in: In) => {
  *       Right(
  *         Map("isCompany" -> true)
  *       ) // success
  *      }
  *    )
  *  )
  * ```
  * or (with implicit conversion)
  * ```
  *  .withValidation(
  *      (in: In) => {
  *       Right(
  *         Map("isCompany" -> true)
  *       ) // success
  *      }
  *  )
  * ```
  * Default is no Initialization.
  */
trait InitProcessHandler[
    In <: Product: InOutCodec
]:
  def init(input: In): Either[InitProcessError, Map[String, Any]]
end InitProcessHandler

object InitProcessHandler:
  def apply[
      In <: Product: InOutCodec
  ](
      funct: In => Either[InitProcessError, Map[String, Any]],
      processLabels: ProcessLabels
  ): InitProcessHandler[In] =
    new InitProcessHandler[In]:
      override def init(in: In): Either[InitProcessError, Map[String, Any]] =
        funct(in)
          .map:
            _ ++ processLabels.toMap

end InitProcessHandler

trait RunWorkHandler[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec
]:
  type RunnerOutput =
    EngineRunContext ?=> Either[RunWorkError, Out]

  def runWork(inputObject: In): RunnerOutput
end RunWorkHandler

case class ServiceHandler[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec,
    ServiceIn: InOutEncoder,
    ServiceOut: InOutDecoder: ClassTag
](
    httpMethod: Method,
    apiUri: In => Uri,
    querySegments: In => Seq[QuerySegmentOrParam],
    inputMapper: In => Option[ServiceIn],
    inputHeaders: In => Map[String, String],
    outputMapper: (ServiceResponse[ServiceOut], In) => Either[ServiceMappingError, Out],
    defaultServiceOutMock: MockedServiceResponse[ServiceOut],
    dynamicServiceOutMock: Option[In => MockedServiceResponse[ServiceOut]] = None,
    serviceInExample: ServiceIn
) extends RunWorkHandler[In, Out]:

  def runWork(
      inputObject: In
  ): RunnerOutput =
    val rRequest = runnableRequest(inputObject)
    for
      optWithServiceMock <- withServiceMock(rRequest, inputObject)
      output <- handleMocking(optWithServiceMock, rRequest).getOrElse(
        summon[EngineRunContext]
          .sendRequest[ServiceIn, ServiceOut](rRequest)
          .flatMap(out => outputMapper(out, inputObject))
      )
    yield output
    end for
  end runWork

  private def runnableRequest(
      inputObject: In
  ): RunnableRequest[ServiceIn] =
    RunnableRequest(
      inputObject,
      httpMethod,
      apiUri(inputObject),
      querySegments(inputObject),
      inputMapper(inputObject),
      inputHeaders(inputObject)
    )

  private def withServiceMock(
      runnableRequest: RunnableRequest[ServiceIn],
      in: In
  )(using context: EngineRunContext): Either[ServiceError, Option[Out]] =
    (
      context.generalVariables.servicesMocked,
      context.generalVariables.outputServiceMock
    ) match
      case (_, Some(json)) =>
        (for
          mockedResponse <- decodeMock[MockedServiceResponse[ServiceOut]](json)
          out <- handleServiceMock(mockedResponse, runnableRequest, in)
        yield out)
          .map(Some.apply)
      case (true, _) =>
        handleServiceMock(dynamicServiceOutMock.map(_(in)).getOrElse(defaultServiceOutMock), runnableRequest, in)
          .map(Some.apply)
      case _ =>
        Right(None)

  end withServiceMock

  private def decodeMock[Out: InOutDecoder](
      json: Json
  ): Either[ServiceMockingError, Out] =
    decodeTo[Out](json.asJson.deepDropNullValues.toString).left
      .map(ex => ServiceMockingError(errorMsg = ex.causeMsg))
  end decodeMock

  private def handleMocking(
      optOutMock: Option[Out],
      runnableRequest: RunnableRequest[ServiceIn]
  )(using context: EngineRunContext): Option[Either[ServiceError, Out]] =
    optOutMock
      .map { mock =>
        context
          .getLogger(getClass)
          .info(s"""Mocked Service: ${niceClassName(this.getClass)}
                   |${requestMsg(runnableRequest)}
                   | - mockedResponse: ${mock.asJson.deepDropNullValues}
                   |""".stripMargin)
        mock
      }
      .map(m => Right(m))
  end handleMocking

  private def handleServiceMock(
      mockedResponse: MockedServiceResponse[ServiceOut],
      runnableRequest: RunnableRequest[ServiceIn],
      in: In
  ): Either[ServiceError, Out] =
    mockedResponse match
      case MockedServiceResponse(_, Right(body), headers) =>
        mapBodyOutput(body, headers, in)
      case MockedServiceResponse(status, Left(body), _) =>
        Left(
          ServiceRequestError(
            status,
            serviceErrorMsg(
              status,
              s"Mocked Error: ${body.map(_.asJson.deepDropNullValues).getOrElse("-")}",
              runnableRequest
            )
          )
        )

  def mapBodyOutput(
      serviceOutput: ServiceOut,
      headers: Seq[Seq[String]],
      in: In
  ) =
    outputMapper(
      ServiceResponse(
        serviceOutput,
        // take correct ones and make a map of it
        headers
          .map(_.toList)
          .collect { case key :: value :: _ => key -> value }
          .toMap
      ),
      in
    )

end ServiceHandler

trait CustomHandler[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec
] extends RunWorkHandler[In, Out]:

end CustomHandler

object CustomHandler:
  def apply[
      In <: Product: InOutCodec,
      Out <: Product: InOutCodec
  ](funct: In => Either[CustomError, Out]): CustomHandler[In, Out] =
    new CustomHandler[In, Out]:
      override def runWork(inputObject: In): RunnerOutput =
        funct(inputObject)
end CustomHandler




© 2015 - 2024 Weber Informatics LLC | Privacy Policy