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

zio.mock.Capability.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019-2022 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.mock

import zio.stacktracer.TracingImplicits.disableAutoTrace
import zio.test.Assertion
import zio.{=!=, EnvironmentTag, IO, LightTypeTag, ZIO, taggedIsSubtype, taggedTagType}

import java.util.UUID

/** A `Capability[R, I, E, A]` represents a capability of environment `R` that takes an input `I` and returns an effect
  * that may fail with an error `E` or produce a single `A`.
  *
  * To represent polymorphic capabilities you must use one of lazy `Capability.Poly` types which allow you to delay the
  * declaration of some types to call site.
  *
  * To construct capability tags you should start by creating a `Mock[R]` and extend publicly available `Effect`,
  * `Method`, `Sink` or `Stream` type members.
  */
protected[mock] abstract class Capability[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](
    val mock: Mock[R]
) extends Capability.Base[R] { self =>

  val inputTag: LightTypeTag  = taggedTagType(implicitly[EnvironmentTag[I]])
  val errorTag: LightTypeTag  = taggedTagType(implicitly[EnvironmentTag[E]])
  val outputTag: LightTypeTag = taggedTagType(implicitly[EnvironmentTag[A]])

  def apply()(implicit ev1: I =:= Unit, ev2: A <:< Unit): Expectation[R] =
    Expectation.Call[R, I, E, A](
      self,
      Assertion.isUnit.asInstanceOf[Assertion[I]],
      ((_: I) => ZIO.unit).asInstanceOf[I => IO[E, A]]
    )

  def apply(assertion: Assertion[I])(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
    Expectation.Call[R, I, E, A](self, assertion, ((_: I) => ZIO.unit).asInstanceOf[I => IO[E, A]])

  def apply(assertion: Assertion[I], result: Result[I, E, A])(implicit ev: I =!= Unit): Expectation[R] =
    Expectation.Call[R, I, E, A](self, assertion, result.io)

  def apply(returns: Result[I, E, A])(implicit ev: I <:< Unit): Expectation[R] =
    Expectation.Call[R, I, E, A](self, Assertion.isUnit.asInstanceOf[Assertion[I]], returns.io)

  def isEqual[R0, I0, E0, A0](that: Capability[R0, I0, E0, A0]): Boolean =
    self.id == that.id &&
      taggedIsSubtype(self.inputTag, that.inputTag) &&
      taggedIsSubtype(self.errorTag, that.errorTag) &&
      taggedIsSubtype(self.outputTag, that.outputTag)
}

object Capability {

  protected abstract class Base[R] {

    val id: UUID = UUID.randomUUID
    val mock: Mock[R]

    /** Render method fully qualified name.
      */
    override val toString: String = {
      val fragments = getClass.getName.replaceAll("\\$", ".").split("\\.")
      fragments.toList.splitAt(fragments.size - 3) match {
        case (namespace, module :: service :: method :: Nil) =>
          s"""${namespace.mkString(".")}.$module.$service.$method"""
        case _                                               => fragments.mkString(".")
      }
    }

  }

  sealed abstract class Unknown

  protected[mock] abstract class Poly[R: EnvironmentTag, I, E, A] extends Base[R]

  object Poly {

    /** Represents capability of environment `R` polymorphic in its input type.
      */
    protected[mock] abstract class Input[R: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](val mock: Mock[R])
        extends Poly[R, Unknown, E, A] {
      self =>

      def of[I: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[I: EnvironmentTag](assertion: Assertion[I])(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[I: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[I: EnvironmentTag](returns: Result[I, E, A])(implicit ev: I <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its error type.
      */
    protected[mock] abstract class Error[R: EnvironmentTag, I: EnvironmentTag, A: EnvironmentTag, E1](val mock: Mock[R])
        extends Poly[R, I, Unknown, A] {
      self =>

      def of[E <: E1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[E <: E1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[E <: E1: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[E <: E1: EnvironmentTag](returns: Result[I, E, A])(implicit ev: I <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its output type.
      */
    protected[mock] abstract class Output[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A1](
        val mock: Mock[R]
    ) extends Poly[R, I, E, Unknown] { self =>

      def of[A <: A1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[A <: A1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[A <: A1: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[A <: A1: EnvironmentTag](returns: Result[I, E, A])(implicit ev: I <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its input and error types.
      */
    protected[mock] abstract class InputError[R: EnvironmentTag, A: EnvironmentTag, E1](val mock: Mock[R])
        extends Poly[R, Unknown, Unknown, A] { self =>

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag](returns: Result[I, E, A])(implicit
          ev: I <:< Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its input and output types.
      */
    protected[mock] abstract class InputOutput[R: EnvironmentTag, E: EnvironmentTag, A1](val mock: Mock[R])
        extends Poly[R, Unknown, E, Unknown] { self =>

      def of[I: EnvironmentTag, A <: A1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[I: EnvironmentTag, A <: A1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[I: EnvironmentTag, A <: A1: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[I: EnvironmentTag, A <: A1: EnvironmentTag](returns: Result[I, E, A])(implicit
          ev: I <:< Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its error and output types.
      */
    protected[mock] abstract class ErrorOutput[R: EnvironmentTag, I: EnvironmentTag, E1, A1](val mock: Mock[R])
        extends Poly[R, I, Unknown, Unknown] { self =>

      def of[E <: E1: EnvironmentTag, A <: A1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](assertion: Assertion[I], result: Result[I, E, A])(
          implicit ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](returns: Result[I, E, A])(implicit
          ev: I <:< Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    /** Represents capability of environment `R` polymorphic in its input, error and output types.
      */
    protected[mock] abstract class InputErrorOutput[R: EnvironmentTag, E1, A1](val mock: Mock[R])
        extends Poly[R, Unknown, Unknown, Unknown] { self =>

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag, A <: A1: EnvironmentTag]: Capability[R, I, E, A] =
        toMethod[R, I, E, A](self)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](
          assertion: Assertion[I]
      )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](
          assertion: Assertion[I],
          result: Result[I, E, A]
      )(implicit
          ev: I =!= Unit
      ): Expectation[R] =
        toExpectation[R, I, E, A](self, assertion, result)

      def of[I: EnvironmentTag, E <: E1: EnvironmentTag, A <: A1: EnvironmentTag](
          returns: Result[I, E, A]
      )(implicit ev: I <:< Unit): Expectation[R] =
        toExpectation[R, I, E, A](self, returns)
    }

    private def toExpectation[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](
        poly: Poly[R, _, _, _],
        assertion: Assertion[I]
    )(implicit ev1: I =!= Unit, ev2: A <:< Unit): Expectation[R] =
      Expectation.Call[R, I, E, A](
        toMethod[R, I, E, A](poly),
        assertion,
        ((_: I) => ZIO.unit).asInstanceOf[I => IO[E, A]]
      )

    private def toExpectation[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](
        poly: Poly[R, _, _, _],
        assertion: Assertion[I],
        result: Result[I, E, A]
    )(implicit ev: I =!= Unit): Expectation[R] =
      Expectation.Call[R, I, E, A](toMethod[R, I, E, A](poly), assertion, result.io)

    private def toExpectation[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](
        poly: Poly[R, _, _, _],
        returns: Result[I, E, A]
    )(implicit ev: I <:< Unit): Expectation[R] =
      Expectation.Call[R, I, E, A](toMethod[R, I, E, A](poly), Assertion.isUnit.asInstanceOf[Assertion[I]], returns.io)

    private def toMethod[R: EnvironmentTag, I: EnvironmentTag, E: EnvironmentTag, A: EnvironmentTag](
        poly: Poly[R, _, _, _]
    ): Capability[R, I, E, A] = new Capability[R, I, E, A](poly.mock) {
      override val id: UUID         = poly.id
      override val toString: String = poly.toString
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy