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

sup.HealthCheck.scala Maven / Gradle / Ivy

package sup

import cats.data.{EitherK, Tuple2K}
import cats.{~>, Applicative, ApplicativeError, Apply, Eq, Functor, Id, Monoid, NonEmptyParallel}
import cats.implicits._
import cats.effect.kernel.implicits._
import cats.effect.kernel.GenConcurrent

/** A health check.
  * F is the effect of making a healthcheck (e.g. IO for calls to external systems).
  *
  * H is the container of results. See [[HealthResult]] for examples.
  */
abstract class HealthCheck[F[_], H[_]] {
  def check: F[HealthResult[H]]

  def leftMapK[G[_]](f: F ~> G): HealthCheck[G, H] =
    transform(f(_))

  def through[G[_], I[_]](mod: HealthCheckMod[F, H, G, I]): HealthCheck[G, I] = mod(this)

  def transform[G[_], I[_]](f: F[HealthResult[H]] => G[HealthResult[I]]): HealthCheck[G, I] =
    HealthCheck.liftF(f(check))

  def mapK[I[_]](f: H ~> I)(implicit F: Functor[F]): HealthCheck[F, I] = mapResult(_.mapK(f))

  def mapResult[I[_]](f: HealthResult[H] => HealthResult[I])(implicit F: Functor[F]): HealthCheck[F, I] =
    HealthCheck.liftF(check.map(f))
}

object HealthCheck extends HealthCheckTaglessInstances {

  /** A healthcheck that always returns the supplied health value.
    */
  def const[F[_]: Applicative, H[_]: Applicative](health: Health): HealthCheck[F, H] = liftF {
    HealthResult.const[H](health).pure[F]
  }

  def liftF[F[_], H[_]](_check: F[HealthResult[H]]): HealthCheck[F, H] = new HealthCheck[F, H] {
    override val check: F[HealthResult[H]] = _check
  }

  /** Lifts a Boolean-returning action to a healthcheck that yields Sick if the action returns false.
    */
  def liftFBoolean[F[_]: Functor](fb: F[Boolean]): HealthCheck[F, Id] =
    liftF(fb.map(Health.fromBoolean.andThen(HealthResult.one)))

  /** Combines two healthchecks by running the first one and recovering with the second one in case of failure in F.
    *
    * If H and I are the same, the result's EitherK can be combined to a single H/I container using `mods.mergeEitherK`.
    */
  def either[F[_], H[_], I[_]](
    a: HealthCheck[F, H],
    b: HealthCheck[F, I]
  )(
    implicit F: ApplicativeError[F, _]
  ): HealthCheck[F, EitherK[H, I, *]] =
    liftF {
      F.handleErrorWith(
        a.check.map(ar => EitherK.left[I](ar.value))
      )(_ => b.check.map(br => EitherK.right[H](br.value)))
        .map(HealthResult(_))
    }

  /** Combines two healthchecks by running them both, then wrapping the result in a Tuple2K.
    *
    * If H and I are the same, the result's Tuple2K can be combined to a single H/I container using `mods.mergeTuple2K`.
    */
  def tupled[F[_]: Apply, H[_], I[_]](a: HealthCheck[F, H], b: HealthCheck[F, I]): HealthCheck[F, Tuple2K[H, I, *]] =
    liftF {
      (a.check, b.check).mapN((ac, bc) => HealthResult(Tuple2K(ac.value, bc.value)))
    }

  /** Combines two healthchecks by running them in parallel, then wrapping the result in a Tuple2K.
    *
    * If H and I are the same, the result's Tuple2K can be combined to a single H/I container using `mods.mergeTuple2K`.
    */
  def parTupled[F[_]: NonEmptyParallel, H[_], I[_]](
    a: HealthCheck[F, H],
    b: HealthCheck[F, I]
  ): HealthCheck[F, Tuple2K[H, I, *]] =
    liftF {
      (a.check, b.check).parMapN((ac, bc) => HealthResult(Tuple2K(ac.value, bc.value)))
    }

  /** Races two healthchecks against each other.
    * The first one to complete with a result (regardless of whether healthy or sick) will cancel the other.
    *
    * If H and I are the same, the result's EitherK can be combined to a single H/I container using `mods.mergeEitherK`.
    */
  def race[F[_], H[_], I[_]](
    a: HealthCheck[F, H],
    b: HealthCheck[F, I]
  )(
    implicit F: GenConcurrent[F, _]
  ): HealthCheck[F, EitherK[H, I, *]] =
    liftF {
      GenConcurrent[F].race(a.check, b.check).map(e => HealthResult(EitherK(e.bimap(_.value, _.value))))
    }

  implicit def checkMonoid[F[_]: Applicative, H[_]: Applicative](
    implicit M: Monoid[Health]
  ): Monoid[HealthCheck[F, H]] =
    new Monoid[HealthCheck[F, H]] {
      override val empty: HealthCheck[F, H] = HealthCheck.const[F, H](M.empty)

      override def combine(x: HealthCheck[F, H], y: HealthCheck[F, H]): HealthCheck[F, H] = liftF {
        Applicative.monoid[F, HealthResult[H]].combine(x.check, y.check)
      }
    }

  implicit def healthCheckEq[F[_], H[_]](implicit F: Eq[F[HealthResult[H]]]): Eq[HealthCheck[F, H]] = Eq.by(_.check)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy