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

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

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

import camundala.bpmn.*
import camundala.domain.*
import camundala.worker.CamundalaWorkerError.*

trait WorkerDsl[In <: Product: InOutCodec,
  Out <: Product: InOutCodec]:

  protected def engineContext: EngineContext

  protected def logger: WorkerLogger

  // needed that it can be called from CSubscriptionPostProcessor
  def worker: Worker[In, Out, ?]

  def topic: String = worker.topic

  def runWorkFromWorker(in: In)(using EngineRunContext): Option[Either[RunWorkError, Out]] =
    worker.runWorkHandler
      .map: handler =>
        handler.runWork(in)

  def runWorkFromWorkerUnsafe(in: In)(using EngineRunContext): Either[RunWorkError, Out] =
    runWorkFromWorker(in)
      .get //only if you are sure that there is a handler
    
  extension [T](option: Option[T])
    def toEither[E <: CamundalaWorkerError](
        error: E
    ): Either[E, T] =
      option
        .map(Right(_))
        .getOrElse(
          Left(error)
        )
  end extension // Option

end WorkerDsl

trait InitWorkerDsl[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec,
    InConfig <: Product: InOutCodec
] extends WorkerDsl[In, Out],
      ValidateDsl[In],
      InitProcessDsl[In, InConfig]:

  protected def inOutExample: Process[In, Out]

  lazy val worker: InitWorker[In, Out] =
    InitWorker(inOutExample)
      .validate(ValidationHandler(validate))
      .initProcess(InitProcessHandler(initProcess, inOutExample.processLabels))

end InitWorkerDsl

trait ValidationWorkerDsl[
  In <: Product : InOutCodec,
] extends WorkerDsl[In, NoOutput],
  ValidateDsl[In]:

  protected def inOutExample: ReceiveEvent[In, ?]

  lazy val worker: InitWorker[In, NoOutput] =
    InitWorker(inOutExample)
      .validate(ValidationHandler(validate))

end ValidationWorkerDsl

trait CustomWorkerDsl[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec
] extends WorkerDsl[In, Out],
      ValidateDsl[In],
      RunWorkDsl[In, Out]:

  protected def customTask: CustomTask[In, Out]

  lazy val worker: CustomWorker[In, Out] =
    CustomWorker(customTask)
      .validate(ValidationHandler(validate))
      .runWork(CustomHandler(runWork))

end CustomWorkerDsl

trait ServiceWorkerDsl[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec,
    ServiceIn: InOutEncoder,
    ServiceOut: InOutDecoder
] extends WorkerDsl[In, Out],
      ValidateDsl[In],
      RunWorkDsl[In, Out]:

  lazy val worker: ServiceWorker[In, Out, ServiceIn, ServiceOut] =
    ServiceWorker[In, Out, ServiceIn, ServiceOut](serviceTask)
      .validate(ValidationHandler(validate))
      .runWork(
        ServiceHandler(
          method,
          apiUri,
          querySegments,
          inputMapper,
          inputHeaders,
          outputMapper,
          serviceTask.defaultServiceOutMock,
          serviceTask.serviceInExample
        )
      )

  // required
  protected def serviceTask: ServiceTask[In, Out, ServiceIn, ServiceOut]
  protected def apiUri(in: In): Uri // input must be valid - so no errors
  // optional
  protected def method: Method = Method.GET
  protected def querySegments(in: In): Seq[QuerySegmentOrParam] =
    Seq.empty // input must be valid - so no errors
      // mocking out from outService and headers
  protected def inputMapper(in: In): Option[ServiceIn] = None // input must be valid - so no errors
  protected def inputHeaders(in: In): Map[String, String] =
    Map.empty // input must be valid - so no errors
  protected def outputMapper(
      serviceOut: ServiceResponse[ServiceOut],
      in: In
  ): Either[ServiceMappingError, Out] = defaultOutMapper(serviceOut, in)

  /** Run the Work is done by the handler. If you want a different behavior, you need to use the
    * CustomWorkerDsl
    */
  final def runWork(
      inputObject: In
  ): Either[CustomError, Out] = Right(serviceTask.out)

  private def defaultOutMapper(
      serviceResponse: ServiceResponse[ServiceOut],
      in: In
  ): Either[ServiceMappingError, Out] =
    serviceResponse.outputBody match
      case _: NoOutput => Right(serviceTask.out)
      case Some(_: NoOutput) => Right(serviceTask.out)
      case None => Right(serviceTask.out)
      case _ =>
        Left(ServiceMappingError(s"There is an outputMapper missing for '${getClass.getName}'."))
  end defaultOutMapper

  protected def queryKeys(ks: String*): Seq[QuerySegmentOrParam] =
    ks.map(QuerySegmentOrParam.Key(_))

  protected def queryKeyValues(kvs: (String, Any)*): Seq[QuerySegmentOrParam] =
    kvs.map { case k -> v => QuerySegmentOrParam.KeyValue(k, s"$v") }

  protected def queryValues(vs: Any*): Seq[QuerySegmentOrParam] =
    vs.map(v => QuerySegmentOrParam.Value(s"$v"))

end ServiceWorkerDsl

private trait ValidateDsl[
    In <: Product: InOutCodec
]:

  def validate(in: In): Either[ValidatorError, In] = Right(in)

end ValidateDsl

private trait InitProcessDsl[
    In <: Product: InOutCodec,
    InConfig <: Product: InOutCodec
]:
  protected def engineContext: EngineContext

  protected def customInit(in: In): Map[String, Any] = Map.empty

  // by default the InConfig is initialized
  def initProcess(in: In): Either[InitProcessError, Map[String, Any]] =
    val inConfig = in match
      case i: WithConfig[?] =>
        initConfig(
          i.inConfig.asInstanceOf[Option[InConfig]],
          i.defaultConfig.asInstanceOf[InConfig]
        )
      case _ => Map.empty
    val custom = engineContext.valuesToEngineObject(customInit(in))
    Right(inConfig ++ custom)
  end initProcess

  /** initialize the config of the form of:
    *
    * ```
    * case class InConfig(
    * timerIdentificationNotReceived: Option[String :| Iso8601Duration],
    * timerEBankingContractCheckOpened: Option[String :| CronExpr] =
    * ...
    * )
    * ```
    */
  private def initConfig(
      optConfig: Option[InConfig],
      defaultConfig: InConfig
  ): Map[String, Any] =
    val defaultJson = defaultConfig.asJson.deepDropNullValues
    val r = optConfig.map {
      config =>
        val json = config.asJson.deepDropNullValues
        config.productElementNames
          .map(k =>
            k -> (json.hcursor
              .downField(k).focus, defaultJson.hcursor
              .downField(k).focus)
          ).collect {
            case k -> (Some(j), Some(dj)) if j.isNull =>
              k -> dj
            case k -> (Some(j), _) =>
              k -> j
            case k -> (_, dj) =>
              k -> dj.getOrElse(Json.Null)
          }
          .toMap
    }.getOrElse { // get all defaults
      defaultConfig.productElementNames
        .map(k =>
          k -> defaultJson.hcursor
            .downField(k).focus
        ).collect {
          case k -> Some(j) =>
            k -> j
        }
        .toMap
    }
    engineContext.toEngineObject(r)
  end initConfig

end InitProcessDsl

private trait RunWorkDsl[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec
]:

  def runWork(
      inputObject: In
  ): Either[CustomError, Out]

end RunWorkDsl




© 2015 - 2024 Weber Informatics LLC | Privacy Policy