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

pl.touk.nussknacker.engine.marshall.ProcessMarshaller.scala Maven / Gradle / Ivy

There is a newer version: 1.18.0
Show newest version
package pl.touk.nussknacker.engine.marshall

import cats.data.Validated
import io.circe.{Decoder, Encoder, Json, JsonObject}
import pl.touk.nussknacker.engine.api.CirceUtil
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.canonicalgraph.canonicalnode._
import pl.touk.nussknacker.engine.graph.node.{Filter, FragmentInput, NodeData, Split, Switch}

object ProcessMarshaller {

  import pl.touk.nussknacker.engine.api.CirceUtil._
  import io.circe.generic.extras.semiauto._

  private implicit val nodeDataEncoder: Encoder[NodeData] = deriveConfiguredEncoder

  private implicit val nodeDataDecoder: Decoder[NodeData] = deriveConfiguredDecoder

  private implicit lazy val flatNodeEncode: Encoder[FlatNode] =
    Encoder.apply[NodeData].contramap[FlatNode](_.data)

  private def addFields(fields: (String, Json)*): JsonObject => JsonObject = obj => fields.foldLeft(obj)(_.+:(_))

  private lazy val flatNodeDecode: Decoder[CanonicalNode] =
    Decoder.apply[NodeData].map(FlatNode)

  private lazy val filterEncode: Encoder[FilterNode] =
    Encoder.instance[FilterNode](filter =>
      Encoder[NodeData]
        .apply(filter.data)
        .mapObject(
          addFields(
            "nextFalse" ->
              Encoder[List[CanonicalNode]].apply(filter.nextFalse)
          )
        )
    )

  private lazy val filterDecode: Decoder[CanonicalNode] =
    for {
      data      <- deriveConfiguredDecoder[Filter]
      nextFalse <- Decoder.instance(j => Decoder[List[CanonicalNode]].tryDecode(j.downField("nextFalse")))
    } yield FilterNode(data, nextFalse)

  private lazy val switchEncode: Encoder[SwitchNode] =
    Encoder.instance[SwitchNode](switch =>
      Encoder[NodeData]
        .apply(switch.data)
        .mapObject(
          addFields(
            "nexts"       -> Encoder[List[Case]].apply(switch.nexts),
            "defaultNext" -> Encoder[List[CanonicalNode]].apply(switch.defaultNext)
          )
        )
    )

  private lazy val switchDecode: Decoder[CanonicalNode] =
    for {
      data        <- deriveConfiguredDecoder[Switch]
      nexts       <- Decoder.instance(j => Decoder[List[Case]].tryDecode(j downField "nexts"))
      defaultNext <- Decoder.instance(j => Decoder[List[CanonicalNode]].tryDecode(j downField "defaultNext"))
    } yield SwitchNode(data, nexts, defaultNext)

  private implicit lazy val splitEncode: Encoder[SplitNode] =
    Encoder.instance[SplitNode](switch =>
      Encoder[NodeData]
        .apply(switch.data)
        .mapObject(
          addFields(
            "nexts" -> Encoder[List[List[CanonicalNode]]].apply(switch.nexts)
          )
        )
    )

  private lazy val splitDecode: Decoder[CanonicalNode] =
    for {
      data  <- deriveConfiguredDecoder[Split]
      nexts <- Decoder.instance(j => Decoder[List[List[CanonicalNode]]].tryDecode(j downField "nexts"))
    } yield SplitNode(data, nexts)

  private lazy val fragmentEncode: Encoder[Fragment] =
    Encoder.instance[Fragment](fragment =>
      Encoder[NodeData]
        .apply(fragment.data)
        .mapObject(
          addFields(
            "outputs" -> Encoder[Map[String, List[CanonicalNode]]].apply(fragment.outputs)
          )
        )
    )

  private lazy val fragmentDecode: Decoder[CanonicalNode] =
    for {
      data  <- deriveConfiguredDecoder[FragmentInput]
      nexts <- Decoder.instance(j => Decoder[Map[String, List[CanonicalNode]]].tryDecode(j downField "outputs"))
    } yield Fragment(data, nexts)

  private implicit lazy val nodeEncode: Encoder[CanonicalNode] =
    Encoder.instance[CanonicalNode] {
      case flat: FlatNode     => flatNodeEncode(flat)
      case filter: FilterNode => filterEncode(filter)
      case switch: SwitchNode => switchEncode(switch)
      case split: SplitNode   => splitEncode(split)
      case fragment: Fragment => fragmentEncode(fragment)
    }

  // order is important here! flatNodeDecode has to be the last
  // TODO: this can lead to difficult to debug errors, when e.g. fragment is incorrect it'll be parsed as flatNode...
  private implicit lazy val nodeDecode: Decoder[CanonicalNode] =
    filterDecode or switchDecode or splitDecode or fragmentDecode or flatNodeDecode

  private implicit lazy val caseDecode: Decoder[Case] = deriveConfiguredDecoder

  private implicit lazy val caseEncode: Encoder[Case] = deriveConfiguredEncoder

  implicit lazy val canonicalProcessEncoder: Encoder[CanonicalProcess] = deriveConfiguredEncoder

  implicit lazy val canonicalProcessDecoder: Decoder[CanonicalProcess] = deriveConfiguredDecoder

  def fromJsonUnsafe(jsonString: String): CanonicalProcess =
    fromJson(jsonString).valueOr(err => throw new IllegalArgumentException("Unmarshalling errors: " + err))

  def fromJsonUnsafe(json: Json): CanonicalProcess =
    fromJson(json).valueOr(err => throw new IllegalArgumentException("Unmarshalling errors: " + err))

  def fromJson(jsonString: String): Validated[String, CanonicalProcess] =
    Validated
      .fromEither(CirceUtil.decodeJson[Json](jsonString))
      .leftMap(_.getMessage)
      .andThen(fromJson)

  def fromJson(json: Json): Validated[String, CanonicalProcess] =
    Validated.fromEither(Decoder[CanonicalProcess].decodeJson(json)).leftMap(_.getMessage)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy