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

camundala.bpmn.CamundaVariables.scala Maven / Gradle / Ivy

package camundala
package bpmn

import camundala.domain.*
import io.circe.*
import io.circe.Json.*
import io.circe.syntax.*

import java.time.LocalDateTime
import scala.annotation.tailrec

sealed trait CamundaVariable:
  def value: Any

object CamundaVariable:

  implicit val encodeCamundaVariable: Encoder[CamundaVariable] =
    Encoder.instance {
      case v: CString => v.asJson
      case v: CInteger => v.asJson
      case v: CLong => v.asJson
      case v: CDouble => v.asJson
      case v: CBoolean => v.asJson
      case v: CFile => v.asJson
      case v: CJson => v.asJson
      case CNull => Json.Null
    }

  given Schema[CamundaVariable] =
    Schema.derived

  given Schema[CString] = Schema.derived
  given Encoder[CString] = deriveEncoder
  given Decoder[CString] = deriveDecoder

  given Schema[CInteger] = Schema.derived
  given Encoder[CInteger] = deriveEncoder
  given Decoder[CInteger] = deriveDecoder

  given Schema[CLong] = Schema.derived
  given Encoder[CLong] = deriveEncoder
  given Decoder[CLong] = deriveDecoder

  given Schema[CDouble] = Schema.derived
  given Encoder[CDouble] = deriveEncoder
  given Decoder[CDouble] = deriveDecoder

  given Schema[CBoolean] = Schema.derived
  given Encoder[CBoolean] = deriveEncoder
  given Decoder[CBoolean] = deriveDecoder

  given Schema[CFile] = Schema.derived
  given Encoder[CFile] = deriveEncoder
  given Decoder[CFile] = deriveDecoder

  given Schema[CFileValueInfo] =
    Schema.derived
  given Encoder[CFileValueInfo] =
    deriveEncoder
  given Decoder[CFileValueInfo] =
    deriveDecoder

  given Schema[CJson] = Schema.derived
  given Encoder[CJson] = deriveEncoder
  given Decoder[CJson] = deriveDecoder

  def toCamunda[T <: Product: Encoder](
      products: Seq[T]
  ): Seq[Map[String, CamundaVariable]] =
    products.map(toCamunda)

  def toCamunda[T <: Product: Encoder](
      product: T
  ): Map[String, CamundaVariable] =
    product.productElementNames
      .zip(product.productIterator)
     // .filterNot { case _ -> v => v.isInstanceOf[None.type] } // don't send null
      .map { case (k, v) => k -> objectToCamunda(product, k, v) }
      .toMap

  @tailrec
  def objectToCamunda[T <: Product: Encoder](
      product: T,
      key: String,
      value: Any
  ): CamundaVariable =
    value match
      case None => CNull
      case Some(v) => objectToCamunda(product, key, v)
      case f @ FileInOut(fileName, _, mimeType) =>
        CFile(
          f.contentAsBase64,
          CFileValueInfo(
            fileName,
            mimeType
          )
        )
      case v: (Product | Iterable[?] | Map[?, ?]) =>
        product.asJson.deepDropNullValues.hcursor
          .downField(key)
          .as[Json] match {
            case Right(str) if str.isString => CString(str.asString.get) // Pure Enum!
            case Right(v) => CJson(v.toString)
            case Left(ex) => throwErr(s"$key of $v could NOT be Parsed to a JSON!\n$ex")
          }

      case v =>
        valueToCamunda(v)

  def valueToCamunda(value: Any): CamundaVariable =
    value match
      case v: String =>
        CString(v)
      case v: Int =>
        CInteger(v)
      case v: Long =>
        CLong(v)
      case v: Boolean =>
        CBoolean(v)
      case v: Float =>
        CDouble(v.toDouble)
      case v: Double =>
        CDouble(v)
      case v: scala.reflect.Enum =>
        CString(v.toString)
      case ldt: LocalDateTime =>
        CString(ldt.toString)
      case other if other == null =>
        CNull
      case v: Json =>
        CJson(v.toString)
      case other =>
        throwErr(s"Unexpected Value to map to CamundaVariable: $other")

  case object CNull extends CamundaVariable:
    val value: Null = null

    private val `type`: String = "String"
  case class CString(value: String, private val `type`: String = "String")
      extends CamundaVariable
  case class CInteger(value: Int, private val `type`: String = "Integer")
      extends CamundaVariable
  case class CLong(value: Long, private val `type`: String = "Long")
      extends CamundaVariable
  case class CBoolean(value: Boolean, private val `type`: String = "Boolean")
      extends CamundaVariable

  case class CDouble(value: Double, private val `type`: String = "Double")
      extends CamundaVariable

  case class CFile(
      @description("The File's content as Base64 encoded String.")
      value: String,
      valueInfo: CFileValueInfo,
      private val `type`: String = "File"
  ) extends CamundaVariable

  case class CFileValueInfo(
      filename: String,
      mimetype: Option[String]
  )

  case class CJson(value: String, private val `type`: String = "Json")
      extends CamundaVariable

  implicit val decodeCamundaVariable: Decoder[CamundaVariable] =
    (c: HCursor) =>
      for
        valueType <- c.downField("type").as[String]
        anyValue = c.downField("value")
        value <- decodeValue(valueType, anyValue, c.downField("valueInfo"))
      yield value

  def decodeValue(
      valueType: String,
      anyValue: ACursor,
      valueInfo: ACursor
  ): Either[DecodingFailure, CamundaVariable] =
    valueType match
      case "Null" => Right(CNull)
      case "Boolean" => anyValue.as[Boolean].map(CBoolean(_))
      case "Integer" => anyValue.as[Int].map(CInteger(_))
      case "Long" => anyValue.as[Long].map(CLong(_))
      case "Double" => anyValue.as[Double].map(CDouble(_))
      case "Json" => anyValue.as[String].map(CJson(_))
      case "File" =>
        valueInfo.as[CFileValueInfo].map(vi => CFile("not_set", vi))
      case _ => anyValue.as[String].map(CString(_))

  type JsonToCamundaValue = CamundaVariable | Map[String, CamundaVariable] |
    Seq[Any]

  def jsonToCamundaValue(json: Json): JsonToCamundaValue =

    val folder: Json.Folder[JsonToCamundaValue] =
      new Json.Folder[JsonToCamundaValue] {
        def onNull: CamundaVariable = CNull

        def onBoolean(value: Boolean): CamundaVariable = CBoolean(value)

        def onNumber(value: JsonNumber): CamundaVariable =
          value.toBigDecimal
            .map {
              case v if v.isValidInt => CInteger(v.intValue)
              case v if v.isValidLong => CLong(v.longValue)
              case v => CDouble(v.doubleValue)
            }
            .getOrElse(CDouble(value.toDouble))

        def onString(value: String): CamundaVariable = CString(value)

        def onArray(value: Vector[Json]): JsonToCamundaValue =
          value.collect {
            case v if !v.isNull => v.foldWith(this)
          }

        def onObject(value: JsonObject): JsonToCamundaValue =
          value
            .filter { case (_, v) => !v.isNull }
            .toMap
            .map((k, v) =>
              k -> (jsonToCamundaValue(v) match
                case cv: CamundaVariable => cv
                case _: (Map[?, ?] | Seq[?]) => CJson(v.toString)
              )
            )
      }
    json.foldWith(folder)

  end jsonToCamundaValue

end CamundaVariable




© 2015 - 2025 Weber Informatics LLC | Privacy Policy