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

colibri.Observer.scala Maven / Gradle / Ivy

The newest version!
package colibri

import cats.effect.{Sync, SyncIO, IO}
import cats.{MonoidK, ContravariantMonoidal}
import colibri.helpers._
import scala.concurrent.Promise

import scala.util.control.NonFatal

trait Observer[-A] {
  def unsafeOnNext(value: A): Unit
  def unsafeOnError(error: Throwable): Unit
}
object Observer    {

  object Empty extends Observer[Any] {
    @inline def unsafeOnNext(value: Any): Unit        = ()
    @inline def unsafeOnError(error: Throwable): Unit = UnhandledErrorReporter.errorSubject.unsafeOnNext(error)
  }

  @inline def empty = Empty

  def lift[G[_]: Sink, A](sink: G[A]): Observer[A] = sink match {
    case sink: Observer[A @unchecked] => sink
    case _                            =>
      new Observer[A] {
        def unsafeOnNext(value: A): Unit          = Sink[G].unsafeOnNext(sink)(value)
        def unsafeOnError(error: Throwable): Unit = Sink[G].unsafeOnError(sink)(error)
      }
  }

  @inline def createUnrecovered[A](
      consume: A => Unit,
      failure: Throwable => Unit = UnhandledErrorReporter.errorSubject.unsafeOnNext,
  ): Observer[A] =
    new Observer[A] {
      def unsafeOnNext(value: A): Unit          = consume(value)
      def unsafeOnError(error: Throwable): Unit = failure(error)
    }

  def foreach[A](consume: A => Unit): Observer[A] = Observer.create(consume)

  def create[A](consume: A => Unit, failure: Throwable => Unit = UnhandledErrorReporter.errorSubject.unsafeOnNext): Observer[A] =
    new Observer[A] {
      def unsafeOnNext(value: A): Unit          = recovered(consume(value), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = failure(error)
    }

  def createFromEither[A](f: Either[Throwable, A] => Unit): Observer[A] =
    new Observer[A] {
      def unsafeOnNext(value: A): Unit          = recovered(f(Right(value)), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = f(Left(error))
    }

  def createFromPromise[A](promise: Promise[A]): Observer[A] =
    new Observer[A] {
      def unsafeOnNext(value: A): Unit          = { promise.trySuccess(value); () }
      def unsafeOnError(error: Throwable): Unit = { promise.tryFailure(error); () }
    }

  def product[A, B](fa: Observer[A], fb: Observer[B]): Observer[(A, B)] = new Observer[(A, B)] {
    def unsafeOnNext(value: (A, B)): Unit     = {
      fa.unsafeOnNext(value._1)
      fb.unsafeOnNext(value._2)
    }
    def unsafeOnError(error: Throwable): Unit = {
      fa.unsafeOnError(error)
      fb.unsafeOnError(error)
    }
  }

  def debugLog[A]: Observer[A]                 = debugLog("")
  def debugLog[A](prefix: String): Observer[A] =
    new Observer[A] {
      private var index = 0

      def unsafeOnNext(value: A): Unit          = {
        println(s"$index: $prefix - $value")
        index += 1
      }
      def unsafeOnError(error: Throwable): Unit = {
        println(s"ERROR: $prefix - $error")
      }
    }

  @inline def combine[A](sinks: Observer[A]*): Observer[A] = combineIterable(sinks)

  def combineIterable[A](sinks: Iterable[Observer[A]]): Observer[A] = new Observer[A] {
    def unsafeOnNext(value: A): Unit          = sinks.foreach(_.unsafeOnNext(value))
    def unsafeOnError(error: Throwable): Unit = sinks.foreach(_.unsafeOnError(error))
  }

  implicit object liftSink extends LiftSink[Observer] {
    @inline def lift[G[_]: Sink, A](sink: G[A]): Observer[A] = Observer.lift(sink)
  }

  implicit object sink extends Sink[Observer] {
    @inline def unsafeOnNext[A](sink: Observer[A])(value: A): Unit          = sink.unsafeOnNext(value)
    @inline def unsafeOnError[A](sink: Observer[A])(error: Throwable): Unit = sink.unsafeOnError(error)
  }

  implicit object catsInstances extends ContravariantMonoidal[Observer] with MonoidK[Observer] {
    @inline override def empty[T]                                    = Observer.empty
    @inline override def combineK[T](a: Observer[T], b: Observer[T]) = Observer.combine(a, b)

    @inline override def contramap[A, B](fa: Observer[A])(f: B => A): Observer[B]          = fa.contramap(f)
    @inline override def unit: Observer[Unit]                                              = Observer.empty
    @inline override def product[A, B](fa: Observer[A], fb: Observer[B]): Observer[(A, B)] = Observer.product(fa, fb)
  }

  @inline implicit class Operations[A](private val sink: Observer[A]) extends AnyVal {
    def liftSource[G[_]: LiftSink]: G[A] = LiftSink[G].lift(sink)

    def contramap[B](f: B => A): Observer[B] = new Observer[B] {
      def unsafeOnNext(value: B): Unit          = recovered(sink.unsafeOnNext(f(value)), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contramapEither[B](f: B => Either[Throwable, A]): Observer[B] = new Observer[B] {
      def unsafeOnNext(value: B): Unit          = recovered(
        f(value) match {
          case Right(value) => sink.unsafeOnNext(value)
          case Left(error)  => sink.unsafeOnError(error)
        },
        unsafeOnError,
      )
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contramapFilter[B](f: B => Option[A]): Observer[B] = new Observer[B] {
      def unsafeOnNext(value: B): Unit          = recovered(f(value).foreach(sink.unsafeOnNext), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contracollect[B](f: PartialFunction[B, A]): Observer[B] = new Observer[B] {
      def unsafeOnNext(value: B): Unit          = recovered({ f.runWith(sink.unsafeOnNext)(value); () }, unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contrafilter(f: A => Boolean): Observer[A] = new Observer[A] {
      def unsafeOnNext(value: A): Unit          = recovered(if (f(value)) sink.unsafeOnNext(value), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contramapIterable[B](f: B => Iterable[A]): Observer[B] = new Observer[B] {
      def unsafeOnNext(value: B): Unit          = recovered(f(value).foreach(sink.unsafeOnNext(_)), unsafeOnError)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def contraflattenIterable: Observer[Iterable[A]]        = contramapIterable(identity)
    def contraflattenEither: Observer[Either[Throwable, A]] = contramapEither(identity)
    def contraflattenOption: Observer[Option[A]]            = contramapFilter(identity)

    // TODO return effect
    def contrascan[B](seed: A)(f: (A, B) => A): Observer[B] = new Observer[B] {
      private var state                         = seed
      def unsafeOnNext(value: B): Unit          = recovered(
        {
          val result = f(state, value)
          state = result
          sink.unsafeOnNext(result)
        },
        unsafeOnError,
      )
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def as(value: A): Observer[Any] = sink.contramap(_ => value)

    def tap(f: A => Unit): Observer[A] = new Observer[A] {
      def unsafeOnNext(value: A): Unit          = {
        f(value)
        sink.unsafeOnNext(value)
      }
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def tapFailed(f: Throwable => Unit): Observer[A] = new Observer[A] {
      def unsafeOnNext(value: A): Unit          = sink.unsafeOnNext(value)
      def unsafeOnError(error: Throwable): Unit = {
        f(error)
        sink.unsafeOnError(error)
      }
    }

    def combine(obs: Observer[A]): Observer[A] = Observer.combine(sink, obs)

    def doOnNext(f: A => Unit): Observer[A] = new Observer[A] {
      def unsafeOnNext(value: A): Unit          = f(value)
      def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error)
    }

    def doOnError(f: Throwable => Unit): Observer[A] = new Observer[A] {
      def unsafeOnNext(value: A): Unit          = sink.unsafeOnNext(value)
      def unsafeOnError(error: Throwable): Unit = f(error)
    }

    def dropOnNext: Observer[A]  = doOnNext(_ => ())
    def dropOnError: Observer[A] = doOnError(_ => ())

    def redirect[B](transform: Observable[B] => Observable[A]): Connectable[Observer[B]] = {
      val handler = Subject.publish[B]()
      val source  = transform(handler)
      Connectable(handler, () => source.unsafeSubscribe(sink))
    }

    def redirectWithLatest[B](transform: Observable[B] => Observable[A]): Connectable[Observer[B]] = {
      val handler = Subject.replayLatest[B]()
      val source  = transform(handler)
      Connectable(handler, () => source.unsafeSubscribe(sink))
    }

    def onNextF[F[_]: Sync](value: A): F[Unit] = Sync[F].delay(sink.unsafeOnNext(value))
    def onNextIO(value: A): IO[Unit]           = onNextF[IO](value)
    def onNextSyncIO(value: A): SyncIO[Unit]   = onNextF[SyncIO](value)

    def onErrorF[F[_]: Sync](error: Throwable): F[Unit] = Sync[F].delay(sink.unsafeOnError(error))
    def onErrorIO(error: Throwable): IO[Unit]           = onErrorF[IO](error)
    def onErrorSyncIO(error: Throwable): SyncIO[Unit]   = onErrorF[SyncIO](error)
  }

  @inline implicit class UnitOperations(private val sink: Observer[Unit]) extends AnyVal {
    @inline def void: Observer[Any] = sink.as(())
  }

  @inline implicit class ThrowableOperations(private val sink: Observer[Throwable]) extends AnyVal {
    def failed: Observer[Any] = Observer.createUnrecovered(_ => (), sink.unsafeOnNext(_))
  }

  private def recovered(action: => Unit, unsafeOnError: Throwable => Unit): Unit = try action
  catch { case NonFatal(t) => unsafeOnError(t) }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy