Giter VIP home page Giter VIP logo

opinionated-zio's Introduction

Opinionated ZIO

A collection of opinionated extensions and helpers around ZIO and vanilla Scala 3 (and only Scala 3).

Maven Central Sonatype Nexus (Snapshots)

Installation

Check the badge above, or the latest GitHub release for the latest version to replace x.y.z.

sbt

libraryDependencies += "com.alterationx10" %% "opinionated-zio" % "x.y.z"
libraryDependencies += "com.alterationx10" %% "opinionated-zio-test" % "x.y.z" % Test

scala cli

//> using dep com.alterationx10::opinionated-zio:x.y.z
//> using test.dep com.alterationx10::opinionated-zio-test:x.y.z

mill

ivy"com.alterationx10::opinionated-zio:x.y.z"
ivy"com.alterationx10::opinionated-zio-test:x.y.z"

Example Usages

Everything is bundled into one package, and to use it, you only need to

import opinons.*

Below are some example usages, centered around the file they're located in. You can also see usage examples in the test suite.

Options

// Wrap a pure value in Option.apply
val anOpt: Option[String] = "example".opt

// Wrap a pure value in Some
val someOpt: Option[String] = "example".some

Trys

// Wraps the result of a Try in Option.apply, converts the Try to 
// an option, and then flattens
val safeTryOpt: Option[String] = Try(null).safeOpt // returns None

ZIO

// Wraps a value in a ZLayer.succeed
val stringLayer: ULayer[String] = "layer".ulayer

// Wraps a value in ZIO.succeed
val stringZIO: UIO[String] = "effect".uio

// Wraps an effect in ZLayer
val effectLayer: ZLayer[Any, Throwable, String] =
  ZIO.attempt("layer").zlayer

// Map a case class to a typelevel config path
case class Config(a: String, b: Int)

val configLayer: Layer[ReadError[String], Config] =
  ConfigLayer[Config]("some.config.path")

// Automatically derive a generic ZLayer
trait Service

// My markdown formatter keeps moving 'derives AutoLayer', so I added it 
// behind a comment line here :-(
case class ServiceImpl(p: Int, cfg: Config) extends Service //derives AutoLayer

val implLayer: ZLayer[Int & Config, Nothing, ServiceImpl] =
  AutoLayer[ServiceImpl]

val superLayer: ZLayer[Int & Config, Nothing, Service] =
  AutoLayer.as[Service, ServiceImpl]

Example Test Library Usages

Everything is bundled into one package for the test library as well, and to use it, you only need to

import test.opinons.*

More docs to come, but please check the corresponding tests for examples.

Assertions

// Wrap a value in Assertion.equalTo
val eq1: Assertion[Int] = 1.eqTo

// Negates wrapping a value in Assertion.EqualTo
val neq1: Assertion[Int] = 1.neqTo

Expectations

// Wrap a value in Expectation.value
val expect1: Result[Any, Nothing, Int] = 1.expected

// Wrap a value in Expectation.failure
val fail: Result[Any, Throwable, Nothing] = new Exception("boom").expectedF

Gens

ZIO has Gens for generative testing, but I also like to use them to make instances of models in other tests (i.e. integration tests for DB, etc...).

case class Thing(a: String)

// Am implicit Gen[Any, Thing] will need to be in scope.
// You can use something like this to derive a Gen if needed
// given Gen[Any, Thing] = DeriveGen[Thing]

// Generate a List of 42 random Things
val thingList: UIO[List[Thing]] = GenRandom[Thing](42)

// Generate a random Thing
val thing: UIO[Thing] = GenRandom[Thing]

Mocks

These methods are mostly to add different ergonomics around Expectations as layers. It adds things expectWhen (or whenExpect), expectWhenF (for failures), and .when(???).expect(???) (partially applying the arguments).

Considering the following Service + Mock

  trait SomeService:
    def get(id: Int): Task[String]
    def multi(a: String, b: Int): Task[String]

  object SomeMockService extends Mock[SomeService]:
    object Get   extends Effect[Int, Throwable, String]
    object Multi extends Effect[(String, Int), Throwable, String]

    val compose: URLayer[Proxy, SomeService] =
      ZLayer {
        for {
          proxy <- ZIO.service[Proxy]
        } yield new SomeService:
          override def get(id: RuntimeFlags): Task[String]             = proxy(Get, id)
          override def multi(a: String, b: RuntimeFlags): Task[String] =
            proxy(Multi, a, b)
      }

We could do some the following:

      test("expect/when of values") {
  for {
    result <- ZIO
      .serviceWithZIO[SomeService](_.get(42))
      .provide(SomeMockService.Get.expectWhen("forty two", 42))
    // partial application extensions
    _ <- ZIO
      .serviceWithZIO[SomeService](_.get(42))
      .provide(
        SomeMockService.Get
          .when(42)
          .expect("c")
      )
    // Comparison without extension method
    _ <- ZIO
      .serviceWithZIO[SomeService](_.get(42))
      .provide(
        SomeMockService
          .Get(Assertion.equalTo(42), Expectation.value("forty two"))
      )
    // Mocking a failure
    error <-
      ZIO
        .serviceWithZIO[SomeService](_.get(42))
        .flip
        .provide(
          SomeMockService.Get.expectWhenF(new Exception("boom"), 42)
        )
  } yield assertTrue(
    result == "forty two",
    error.getMessage == "boom"
  )
}

One pain with mocking services that multi-argument are Tuples, but expects a (single) Assertion of that Tuple type.

It would be wonderful to provide assertions on the tuple elements, instead of one blanket assertion of a properly typed tuple. For example, maybe I only care about the value of the first element of a Tuple3, and the others can be anything.

This provides (up to Tuple5 so far, because I'm lazy) extension methods to take a Tuple of Assertions to make it an Assertion of the appropriately typed Tuple, checking the values along the way.

These are generally implemented as:

extension[A, B, C](t: (Assertion[A], Assertion[B], Assertion[C]))
/** Convert a Tuple of Assertions into an Assertion of a Tuple
 */
def asserted: Assertion[(A, B, C)] =
  Assertion.hasField("_1", (tt: (A, B, C)) => tt._1, t._1) &&
    Assertion.hasField("_2", (tt: (A, B, C)) => tt._2, t._2) &&
    Assertion.hasField("_3", (tt: (A, B, C)) => tt._3, t._3)

With these, we can write a Mock expectation like below, where we only care about a specific value for the first method argument, but the second can be anything.

val justA = SomeMockService.Multi
  .when(("a".eqTo, Assertion.anything).asserted)
  .expect("success")

opinionated-zio's People

Contributors

alterationx10 avatar

Stargazers

 avatar  avatar

Watchers

 avatar

opinionated-zio's Issues

Compilation problems with 3.3.2-RC nightly

Hey, I've found this project after failure in Scala 3 Open Community Build.
The problematic code is present in this lines:

case p: Mirror.ProductOf[A] =>
val services =
listOfServices[p.MirroredElemTypes]
val init: ZIO[IAnyType[p.MirroredElemTypes], Nothing, List[
UAnyType[p.MirroredElemTypes]
]] = ZIO.succeed(List.empty)
val flattened =
services.foldLeft(init)((l, z) =>
l.flatMap(_l => z.map(_z => _l :+ _z)),
)
val a: ZIO[IAnyType[p.MirroredElemTypes], Nothing, A] =
flattened.map(deps => p.fromProduct(Tuple.fromArray(deps.toArray)))
new AutoLayer[A]:
override def zlayer(using
pp: ProductOf[A]
): ZLayer[IAnyType[pp.MirroredElemTypes], Nothing, A] = ZLayer {
a.asInstanceOf[ZIO[IAnyType[pp.MirroredElemTypes], Nothing, A]]
}

After some of the latest typer improvements of Scala typer the project would no longer compile. The minimization of this problem can be found in: scala/scala3#18277
In Scala 3.3.0 and 3.3.1 the deps.toArray failed to correctly infer type of p.MirrorElemTypes and used Object instead. This allow to use ClassTag[Object] to allow for conversion of list to array. In later, nightly versions of compiler the infered type is more narrowed, and though ClassTag[Object] can no longer be applied.

As the compiler team have concluded this code should not compile, it is not following the language spec and should be rewritten in the future.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.