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

clue.data.Input.scala Maven / Gradle / Ivy

There is a newer version: 0.40.0
Show newest version
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package clue.data

import cats.Align
import cats.Applicative
import cats.Eq
import cats.Eval
import cats.Functor
import cats.Monad
import cats.Show
import cats.Traverse
import cats.data.Ior
import cats.syntax.all.*
import clue.data.syntax.*
import io.circe.*
import io.circe.syntax.*

import scala.annotation.tailrec

sealed trait Input[+A] {
  def map[B](f: A => B): Input[B] =
    this match {
      case Ignore    => Ignore
      case Unassign  => Unassign
      case Assign(a) => Assign(f(a))
    }

  def fold[B](fundef: => B, funset: => B, fset: A => B): B =
    this match {
      case Ignore    => fundef
      case Unassign  => funset
      case Assign(a) => fset(a)
    }

  def flatten[B](implicit ev: A <:< Input[B]): Input[B] =
    this match {
      case Ignore    => Ignore
      case Unassign  => Unassign
      case Assign(a) => a
    }

  def flatMap[B](f: A => Input[B]): Input[B] =
    map(f).flatten

  def toOption: Option[A] =
    this match {
      case Assign(a) => a.some
      case _         => none
    }
}

case object Ignore                    extends Input[Nothing]
case object Unassign                  extends Input[Nothing]
final case class Assign[+A](value: A) extends Input[A]

object Input {
  def apply[A](a: A): Input[A] = Assign(a)

  /**
   * Alias for `apply`.
   */
  def assign[A](a: A): Input[A] = Input(a)

  def unassign[A]: Input[A] = Unassign

  def ignore[A]: Input[A] = Ignore

  def orIgnore[A](opt: Option[A]): Input[A] =
    opt match {
      case Some(a) => Assign(a)
      case None    => Ignore
    }

  def orUnassign[A](opt: Option[A]): Input[A] =
    opt match {
      case Some(a) => Assign(a)
      case None    => Unassign
    }

  implicit def inputEq[A: Eq]: Eq[Input[A]] =
    new Eq[Input[A]] {
      def eqv(x: Input[A], y: Input[A]): Boolean =
        x match {
          case Assign(ax) =>
            y match {
              case Assign(ay) => ax === ay
              case _          => false
            }
          case Ignore     =>
            y match {
              case Ignore => true
              case _      => false
            }
          case Unassign   =>
            y match {
              case Unassign => true
              case _        => false
            }
        }
    }

  implicit def inputShow[A: Show]: Show[Input[A]] =
    new Show[Input[A]] {
      override def show(t: Input[A]): String = t match {
        case Assign(a) => s"Set(${a.show})"
        case other @ _ => other.toString
      }
    }

  private val IgnoreValue: Json = Json.fromString("<>")

  def dropIgnores(obj: JsonObject): JsonObject = obj.deepFilter((_, value) => value =!= IgnoreValue)

  implicit def inputEncoder[A: Encoder]: Encoder[Input[A]] = new Encoder[Input[A]] {
    override def apply(a: Input[A]): Json = a match {
      case Ignore    => IgnoreValue
      case Unassign  => Json.Null
      case Assign(a) => a.asJson
    }
  }

  implicit def inputDecoder[A: Decoder]: Decoder[Input[A]] = new Decoder[Input[A]] {
    override def apply(c: HCursor): Decoder.Result[Input[A]] =
      c.as[Option[A]].map(_.orUnassign)
  }

  implicit object InputCats extends Monad[Input] with Traverse[Input] with Align[Input] {

    override def pure[A](a: A): Input[A] = Input(a)

    @tailrec
    override def tailRecM[A, B](a: A)(f: A => Input[Either[A, B]]): Input[B] =
      f(a) match {
        case Ignore           => Ignore
        case Unassign         => Unassign
        case Assign(Left(a))  => tailRecM(a)(f)
        case Assign(Right(b)) => Assign(b)
      }

    override def flatMap[A, B](fa: Input[A])(f: A => Input[B]): Input[B] =
      fa.flatMap(f)

    override def traverse[F[_], A, B](
      fa: Input[A]
    )(f: A => F[B])(implicit F: Applicative[F]): F[Input[B]] =
      fa match {
        case Ignore    => F.pure(Ignore)
        case Unassign  => F.pure(Unassign)
        case Assign(a) => F.map(f(a))(Assign(_))
      }

    override def foldLeft[A, B](fa: Input[A], b: B)(f: (B, A) => B): B =
      fa match {
        case Ignore    => b
        case Unassign  => b
        case Assign(a) => f(b, a)
      }

    override def foldRight[A, B](fa: Input[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
      fa match {
        case Ignore    => lb
        case Unassign  => lb
        case Assign(a) => f(a, lb)
      }

    override def functor: Functor[Input] = this

    override def align[A, B](fa: Input[A], fb: Input[B]): Input[Ior[A, B]] =
      alignWith(fa, fb)(identity)

    override def alignWith[A, B, C](fa: Input[A], fb: Input[B])(f: Ior[A, B] => C): Input[C] =
      fa match {
        case Ignore    =>
          fb match {
            case Ignore    => Ignore
            case Unassign  => Unassign
            case Assign(b) => Assign(f(Ior.right(b)))
          }
        case Unassign  =>
          fb match {
            case Ignore    => Ignore
            case Unassign  => Unassign
            case Assign(b) => Assign(f(Ior.right(b)))
          }
        case Assign(a) =>
          fb match {
            case Ignore    => Assign(f(Ior.left(a)))
            case Unassign  => Assign(f(Ior.left(a)))
            case Assign(b) => Assign(f(Ior.both(a, b)))
          }
      }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy