diff --git a/build.sbt b/build.sbt index c4ac3bcd..d2463344 100644 --- a/build.sbt +++ b/build.sbt @@ -1,39 +1,53 @@ -ThisBuild / scalaVersion := "2.13.2" -ThisBuild / crossScalaVersions := Seq("2.12.11", "2.13.2") +ThisBuild / scalaVersion := scala213 ThisBuild / organization := "com.47deg" -addCommandAlias("ci-test", "scalafmtCheckAll; scalafmtSbtCheck; mdoc; testCovered") +addCommandAlias("ci-test", "scalafmtCheckAll; scalafmtSbtCheck; mdoc; ++test") addCommandAlias("ci-docs", "github; mdoc; headerCreateAll; publishMicrosite") addCommandAlias("ci-publish", "github; ci-release") +lazy val scala212 = "2.12.12" +lazy val scala213 = "2.13.5" +lazy val scala3Version = "3.0.0-RC2" +lazy val scala2Versions = Seq(scala212, scala213) +lazy val allScalaVersions = scala2Versions :+ scala3Version + skip in publish := true lazy val fetch = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .settings(commonCrossDependencies) + .settings(crossScalaVersions := allScalaVersions) + lazy val fetchJVM = fetch.jvm -lazy val fetchJS = fetch.js.disablePlugins(ScoverageSbtPlugin) +lazy val fetchJS = fetch.js + .settings(crossScalaVersions := scala2Versions) lazy val `fetch-debug` = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .dependsOn(fetch) .settings(commonCrossDependencies) + .settings(crossScalaVersions := allScalaVersions) + lazy val debugJVM = `fetch-debug`.jvm -lazy val debugJS = `fetch-debug`.js.disablePlugins(ScoverageSbtPlugin) +lazy val debugJS = `fetch-debug`.js + .settings(crossScalaVersions := scala2Versions) lazy val `fetch-examples` = project .dependsOn(fetchJVM, debugJVM) .settings(skip in publish := true) .settings(examplesSettings: _*) + .settings(crossScalaVersions := scala2Versions) lazy val microsite = project .dependsOn(fetchJVM, debugJVM) .settings(docsSettings: _*) .settings(skip in publish := true) .enablePlugins(MicrositesPlugin, MdocPlugin) + .settings(crossScalaVersions := scala2Versions) lazy val documentation = project .dependsOn(fetchJVM) .settings(skip in publish := true) .settings(mdocOut := file(".")) .enablePlugins(MdocPlugin) + .settings(crossScalaVersions := scala2Versions) diff --git a/docs/README.md b/docs/README.md index 1a6b455b..b7f4dd0a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # Fetch -[![Join the chat at https://gitter.im/47deg/fetch](https://badges.gitter.im/47deg/fetch.svg)](https://gitter.im/47deg/fetch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![codecov.io](http://codecov.io/github/47deg/fetch/coverage.svg?branch=master)](http://codecov.io/github/47deg/fetch?branch=master) [![Maven Central](https://img.shields.io/badge/maven%20central-1.2.1-green.svg)](https://oss.sonatype.org/#nexus-search;gav~com.47deg~fetch*) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/47deg/fetch/master/LICENSE) [![Latest version](https://img.shields.io/badge/fetch-1.2.1-green.svg)](https://index.scala-lang.org/47deg/fetch) [![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.15.svg)](http://scala-js.org) [![GitHub Issues](https://img.shields.io/github/issues/47deg/fetch.svg)](https://github.com/47deg/fetch/issues) +[![Join the chat at https://gitter.im/47deg/fetch](https://badges.gitter.im/47deg/fetch.svg)](https://gitter.im/47deg/fetch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Maven Central](https://img.shields.io/badge/maven%20central-1.2.1-green.svg)](https://oss.sonatype.org/#nexus-search;gav~com.47deg~fetch*) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/47deg/fetch/master/LICENSE) [![Latest version](https://img.shields.io/badge/fetch-1.2.1-green.svg)](https://index.scala-lang.org/47deg/fetch) [![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.15.svg)](http://scala-js.org) [![GitHub Issues](https://img.shields.io/github/issues/47deg/fetch.svg)](https://github.com/47deg/fetch/issues) A library for Simple & Efficient data access in Scala and Scala.js diff --git a/fetch-debug/src/main/scala/debug.scala b/fetch-debug/src/main/scala/debug.scala index 2ff2959e..0960198e 100644 --- a/fetch-debug/src/main/scala/debug.scala +++ b/fetch-debug/src/main/scala/debug.scala @@ -64,7 +64,7 @@ object debug { } yield lastRequestEnd - firstRequestStart val durationDoc = duration.fold(Document.empty: Document)((d: Long) => - Document.text("Fetch execution") :: showDuration(d) + Document.text("Fetch execution") :-: showDuration(d) ) durationDoc :/: Document.nest( @@ -80,9 +80,9 @@ object debug { } yield l - f val round = - Document.text(s"[Round ${n}]") :: roundDuration.fold(Document.text(""))(showDuration(_)) + Document.text(s"[Round ${n}]") :-: roundDuration.fold(Document.text(""))(showDuration(_)) - round :: Document.nest( + round :-: Document.nest( 2, pile(r.queries.map(showRequest)) ) @@ -91,9 +91,9 @@ object debug { def showRequest(r: Request): Document = r.request match { case FetchOne(id, d) => - Document.text(s"[Fetch one] From `${d.name}` with id ${id}") :: showDuration(r.duration) + Document.text(s"[Fetch one] From `${d.name}` with id ${id}") :-: showDuration(r.duration) case Batch(ids, d) => - Document.text(s"[Batch] From `${d.name}` with ids ${ids.toList}") :: showDuration( + Document.text(s"[Batch] From `${d.name}` with ids ${ids.toList}") :-: showDuration( r.duration ) } @@ -110,14 +110,14 @@ object debug { Document .text( s"[ERROR] Identity with id `${id}` for data source `${q.data.name}` not found" - ) :: showRoundCount( + ) :-: showRoundCount( err ) case UnhandledException(exc, log) => Document .text( s"[ERROR] Unhandled `${exc.getClass.getName}`: '${exc.getMessage}'" - ) :: showRoundCount( + ) :-: showRoundCount( err ) } diff --git a/fetch-debug/src/main/scala/document.scala b/fetch-debug/src/main/scala/document.scala index 245528b6..07dd7719 100644 --- a/fetch-debug/src/main/scala/document.scala +++ b/fetch-debug/src/main/scala/document.scala @@ -33,10 +33,10 @@ case class DocCons(hd: Document, tl: Document) extends Document * @version 1.0 */ abstract class Document { - def ::(hd: Document): Document = DocCons(hd, this) - def ::(hd: String): Document = DocCons(DocText(hd), this) - def :/:(hd: Document): Document = hd :: DocBreak :: this - def :/:(hd: String): Document = hd :: DocBreak :: this + def :-:(hd: Document): Document = DocCons(hd, this) + def :-:(hd: String): Document = DocCons(DocText(hd), this) + def :/:(hd: Document): Document = hd :-: DocBreak :-: this + def :/:(hd: String): Document = hd :-: DocBreak :-: this /** * Format this document on `writer` and try to set line diff --git a/fetch-examples/src/test/scala/DoobieExample.scala b/fetch-examples/src/test/scala/DoobieExample.scala index dc567806..26fbeaa4 100644 --- a/fetch-examples/src/test/scala/DoobieExample.scala +++ b/fetch-examples/src/test/scala/DoobieExample.scala @@ -82,7 +82,8 @@ object DatabaseExample { def createTransactor[F[_]: Async: ContextShift] = for { - (conn, trans) <- (connectionPool[F](1), transactionPool[F]).tupled + connAndTrans <- (connectionPool[F](1), transactionPool[F]).tupled + (conn, trans) = connAndTrans tx <- H2Transactor .newH2Transactor[F]( @@ -138,13 +139,13 @@ class DoobieExample extends AnyWordSpec with Matchers with BeforeAndAfterAll { createTable(tx) *> authors.traverse(addAuthor(_)(tx)) }) .void - .unsafeRunSync - override def afterAll(): Unit = transactor.use(dropTable(_)).void.unsafeRunSync + .unsafeRunSync() + override def afterAll(): Unit = transactor.use(dropTable(_)).void.unsafeRunSync() "We can fetch one author from the DB" in { val io: IO[(Log, Author)] = Fetch.runLog[IO](Authors.fetchAuthor(1)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Author(1, "William Shakespeare") log.rounds.size shouldEqual 1 @@ -156,7 +157,7 @@ class DoobieExample extends AnyWordSpec with Matchers with BeforeAndAfterAll { val io: IO[(Log, List[Author])] = Fetch.runLog[IO](fetch) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Author(1, "William Shakespeare") :: Author(2, "Charles Dickens") :: Nil log.rounds.size shouldEqual 1 @@ -171,7 +172,7 @@ class DoobieExample extends AnyWordSpec with Matchers with BeforeAndAfterAll { val io: IO[(Log, List[Author])] = Fetch.runLog[IO](fetch) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Author(1, "William Shakespeare") :: Author(2, "Charles Dickens") :: Nil log.rounds.size shouldEqual 2 diff --git a/fetch-examples/src/test/scala/GraphQLExample.scala b/fetch-examples/src/test/scala/GraphQLExample.scala index 77c8fcec..4a76dd98 100644 --- a/fetch-examples/src/test/scala/GraphQLExample.scala +++ b/fetch-examples/src/test/scala/GraphQLExample.scala @@ -32,9 +32,9 @@ case class Project(name: Option[String], languages: List[String], collaborators: case class Repo(name: String) class GraphQLExample extends AnyWordSpec with Matchers { - implicit val executionContext = ExecutionContext.Implicits.global - implicit val t: Timer[IO] = IO.timer(executionContext) - implicit val cs: ContextShift[IO] = IO.contextShift(executionContext) + implicit val executionContext: ExecutionContext = ExecutionContext.Implicits.global + implicit val t: Timer[IO] = IO.timer(executionContext) + implicit val cs: ContextShift[IO] = IO.contextShift(executionContext) def countFetches(r: Request): Int = r.request match { @@ -119,7 +119,7 @@ class GraphQLExample extends AnyWordSpec with Matchers { "We can interpret queries" in { val io = Fetch.runLog[IO](runQuery(query)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Organization( "47deg", @@ -135,7 +135,7 @@ class GraphQLExample extends AnyWordSpec with Matchers { "We can interpret queries with only languages" in { val io = Fetch.runLog[IO](runQuery(langsQuery)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Organization( "47deg", @@ -148,7 +148,7 @@ class GraphQLExample extends AnyWordSpec with Matchers { "We can interpret queries with only collaborators" in { val io = Fetch.runLog[IO](runQuery(collabsQuery)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Organization( "47deg", List( @@ -163,7 +163,7 @@ class GraphQLExample extends AnyWordSpec with Matchers { "We can interpret queries with no nested joins" in { val io = Fetch.runLog[IO](runQuery(orgQuery)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Organization( "47deg", List(Project(Some("fetch"), List(), List()), Project(Some("arrow"), List(), List())) @@ -175,7 +175,7 @@ class GraphQLExample extends AnyWordSpec with Matchers { "We can interpret queries with a limited number of repositories" in { val io = Fetch.runLog[IO](runQuery(repoQuery)) - val (log, result) = io.unsafeRunSync + val (log, result) = io.unsafeRunSync() result shouldEqual Organization( "47deg", diff --git a/fetch/src/main/scala/fetch.scala b/fetch/src/main/scala/fetch.scala index d12011f3..c5a3f515 100644 --- a/fetch/src/main/scala/fetch.scala +++ b/fetch/src/main/scala/fetch.scala @@ -156,7 +156,10 @@ object `package` { y: BlockedRequest[F] ): BlockedRequest[F] = (x.request, y.request) match { - case (a @ FetchOne(aId, ds), b @ FetchOne(anotherId, _)) => + case (a: FetchOne[Any, Any], b: FetchOne[Any, Any]) => + val aId = a.id + val ds = a.data + val anotherId = b.id if (aId == anotherId) { val newRequest = FetchOne(aId, ds) val newResult = x.result.flatMap(() => y.result) @@ -180,7 +183,9 @@ object `package` { BlockedRequest(newRequest, newResult) } - case (a @ FetchOne(oneId, ds), b @ Batch(anotherIds, _)) => + case (a: FetchOne[Any, Any], b: Batch[Any, Any]) => + val oneId = a.id + val ds = a.data val combined = combineIdentities(a, b) val newRequest = Batch(combined, ds) val newResult = CombinationSuspend((r: FetchStatus) => @@ -198,7 +203,9 @@ object `package` { BlockedRequest(newRequest, newResult) - case (a @ Batch(manyId, ds), b @ FetchOne(oneId, _)) => + case (a: Batch[Any, Any], b: FetchOne[Any, Any]) => + val ds = a.data + val oneId = b.id val combined = combineIdentities(a, b) val newRequest = Batch(combined, ds) val newResult = CombinationSuspend((r: FetchStatus) => @@ -215,7 +222,8 @@ object `package` { ) BlockedRequest(newRequest, newResult) - case (a @ Batch(manyId, ds), b @ Batch(otherId, _)) => + case (a: Batch[Any, Any], b: Batch[Any, Any]) => + val ds = a.data val combined = combineIdentities(a, b) val newRequest = Batch(combined, ds) val newResult = x.result.flatMap(() => y.result) @@ -473,9 +481,10 @@ object `package` { T: Timer[F] ): F[(Log, A)] = for { - (log, cache) <- (ref[F, Log](FetchLog()), ref[F, DataCache[F]](cache)).tupled - result <- performRun(fa, cache, Some(log)) - e <- log.get + logAndCache <- (ref[F, Log](FetchLog()), ref[F, DataCache[F]](cache)).tupled + (log, cache) = logAndCache + result <- performRun(fa, cache, Some(log)) + e <- log.get } yield (e, result) } @@ -529,9 +538,11 @@ object `package` { T: Timer[F] ): F[(Log, DataCache[F], A)] = for { - (log, cache) <- (ref[F, Log](FetchLog()), ref[F, DataCache[F]](cache)).tupled - result <- performRun(fa, cache, Some(log)) - (e, c) <- (log.get, cache.get).tupled + logAndCache <- (ref[F, Log](FetchLog()), ref[F, DataCache[F]](cache)).tupled + (log, cache) = logAndCache + result <- performRun(fa, cache, Some(log)) + eAndC <- (log.get, cache.get).tupled + (e, c) = eAndC } yield (e, c, result) } @@ -591,8 +602,9 @@ object `package` { if (performedRequests.isEmpty) Applicative[F].unit else log match { - case Some(l) => l.modify((oldE) => (oldE.append(Round(performedRequests)), oldE)) - case None => Applicative[F].unit + case Some(l) => + l.modify((oldE) => (oldE.append(Round(performedRequests)), oldE)).void + case None => Applicative[F].unit } } yield () } @@ -607,8 +619,8 @@ object `package` { T: Timer[F] ): F[List[Request]] = blocked.request match { - case q @ FetchOne(_, _) => runFetchOne[F](q, ds, blocked.result, cache, log) - case q @ Batch(_, _) => runBatch[F](q, ds, blocked.result, cache, log) + case q: FetchOne[Any, Any] => runFetchOne[F](q, ds, blocked.result, cache, log) + case q: Batch[Any, Any] => runBatch[F](q, ds, blocked.result, cache, log) } } diff --git a/fetch/src/test/scala/FetchAsyncQueryTests.scala b/fetch/src/test/scala/FetchAsyncQueryTests.scala index c73a5155..1babd6c4 100644 --- a/fetch/src/test/scala/FetchAsyncQueryTests.scala +++ b/fetch/src/test/scala/FetchAsyncQueryTests.scala @@ -29,7 +29,7 @@ class FetchAsyncQueryTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual Article(1, "An article with id 1")).unsafeToFuture + io.map(_ shouldEqual Article(1, "An article with id 1")).unsafeToFuture() } "We can combine several async data sources and interpret a fetch into an IO" in { @@ -41,7 +41,7 @@ class FetchAsyncQueryTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual (Article(1, "An article with id 1"), Author(2, "@egg2"))).unsafeToFuture + io.map(_ shouldEqual (Article(1, "An article with id 1"), Author(2, "@egg2"))).unsafeToFuture() } "We can use combinators in a for comprehension and interpret a fetch from async sources into an IO" in { @@ -58,7 +58,7 @@ class FetchAsyncQueryTests extends FetchSpec { Article(1, "An article with id 1"), Article(2, "An article with id 2") ) - ).unsafeToFuture + ).unsafeToFuture() } "We can use combinators and multiple sources in a for comprehension and interpret a fetch from async sources into an IO" in { @@ -83,7 +83,7 @@ class FetchAsyncQueryTests extends FetchSpec { Author(3, "@egg3") ) ) - ).unsafeToFuture + ).unsafeToFuture() } } diff --git a/fetch/src/test/scala/FetchBatchingTests.scala b/fetch/src/test/scala/FetchBatchingTests.scala index 344f9fae..079bc7f2 100644 --- a/fetch/src/test/scala/FetchBatchingTests.scala +++ b/fetch/src/test/scala/FetchBatchingTests.scala @@ -114,7 +114,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 5 totalBatches(log.rounds) shouldEqual 3 - }).unsafeToFuture + }).unsafeToFuture() } "A large fetch to a datasource with a maximum batch size is split and executed in parallel" in { @@ -128,7 +128,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 5 totalBatches(log.rounds) shouldEqual 3 - }).unsafeToFuture + }).unsafeToFuture() } "Fetches to datasources with a maximum batch size should be split and executed in parallel and sequentially when using productR" in { @@ -143,7 +143,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 5 + 5 totalBatches(log.rounds) shouldEqual 3 + 3 - }).unsafeToFuture + }).unsafeToFuture() } "Fetches to datasources with a maximum batch size should be split and executed in parallel and sequentially when using productL" in { @@ -158,7 +158,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 5 + 5 totalBatches(log.rounds) shouldEqual 3 + 3 - }).unsafeToFuture + }).unsafeToFuture() } "A large (many) fetch to a datasource with a maximum batch size is split and executed in sequence" in { @@ -172,7 +172,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 3 totalBatches(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "A large (many) fetch to a datasource with a maximum batch size is split and executed in parallel" in { @@ -186,7 +186,7 @@ class FetchBatchingTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 3 totalBatches(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Very deep fetches don't overflow stack or heap" in { @@ -205,6 +205,6 @@ class FetchBatchingTests extends FetchSpec { io.map({ case (log, result) => result shouldEqual ids.map(_.toString) - }).unsafeToFuture + }).unsafeToFuture() } } diff --git a/fetch/src/test/scala/FetchReportingTests.scala b/fetch/src/test/scala/FetchReportingTests.scala index 72ac8e3c..5c1db381 100644 --- a/fetch/src/test/scala/FetchReportingTests.scala +++ b/fetch/src/test/scala/FetchReportingTests.scala @@ -31,7 +31,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 0 - }).unsafeToFuture + }).unsafeToFuture() } "Single fetches are executed in one round" in { @@ -42,7 +42,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 1 - }).unsafeToFuture + }).unsafeToFuture() } "Single fetches are executed in one round per binding in a for comprehension" in { @@ -56,7 +56,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Single fetches for different data sources are executed in multiple rounds if they are in a for comprehension" in { @@ -70,7 +70,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Single fetches combined with cartesian are run in one round" in { @@ -81,7 +81,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 1 - }).unsafeToFuture + }).unsafeToFuture() } "Single fetches combined with traverse are run in one round" in { @@ -95,7 +95,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "The product of two fetches from the same data source implies batching" in { @@ -106,7 +106,7 @@ class FetchReportingTests extends FetchSpec { io.map({ case (log, result) => log.rounds.size shouldEqual 1 - }).unsafeToFuture + }).unsafeToFuture() } "The product of concurrent fetches of the same type implies everything fetched in batches" in { @@ -133,6 +133,6 @@ class FetchReportingTests extends FetchSpec { log.rounds.size shouldEqual 2 totalBatches(log.rounds) shouldEqual 1 totalFetched(log.rounds) shouldEqual 3 + 1 - }).unsafeToFuture + }).unsafeToFuture() } } diff --git a/fetch/src/test/scala/FetchSyntaxTests.scala b/fetch/src/test/scala/FetchSyntaxTests.scala index d730d0eb..f02b8437 100644 --- a/fetch/src/test/scala/FetchSyntaxTests.scala +++ b/fetch/src/test/scala/FetchSyntaxTests.scala @@ -43,6 +43,6 @@ class FetchSyntaxTests extends FetchSpec { val e1 = io1.handleError(err => 42) val e2 = io2.handleError(err => 42) - (e1, e2).mapN(_ shouldEqual _).unsafeToFuture + (e1, e2).mapN(_ shouldEqual _).unsafeToFuture() } } diff --git a/fetch/src/test/scala/FetchTests.scala b/fetch/src/test/scala/FetchTests.scala index 522dddec..565342ab 100644 --- a/fetch/src/test/scala/FetchTests.scala +++ b/fetch/src/test/scala/FetchTests.scala @@ -36,18 +36,18 @@ class FetchTests extends FetchSpec { def fetch[F[_]: ConcurrentEffect]: Fetch[F, Int] = Fetch.pure[F, Int](42) - Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture() } "We can lift values which have a Data Source to Fetch" in { - Fetch.run[IO](one(1)).map(_ shouldEqual 1).unsafeToFuture + Fetch.run[IO](one(1)).map(_ shouldEqual 1).unsafeToFuture() } "We can map over Fetch values" in { def fetch[F[_]: ConcurrentEffect]: Fetch[F, (Int)] = one(1).map(_ + 1) - Fetch.run[IO](fetch).map(_ shouldEqual 2).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 2).unsafeToFuture() } "We can use fetch inside a for comprehension" in { @@ -57,7 +57,7 @@ class FetchTests extends FetchSpec { t <- one(2) } yield (o, t) - Fetch.run[IO](fetch).map(_ shouldEqual (1, 2)).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual (1, 2)).unsafeToFuture() } "We can mix data sources" in { @@ -67,7 +67,7 @@ class FetchTests extends FetchSpec { m <- many(3) } yield (o, m) - Fetch.run[IO](fetch).map(_ shouldEqual (1, List(0, 1, 2))).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual (1, List(0, 1, 2))).unsafeToFuture() } "We can use Fetch as a cartesian" in { @@ -75,7 +75,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual (1, List(0, 1, 2))).unsafeToFuture + io.map(_ shouldEqual (1, List(0, 1, 2))).unsafeToFuture() } "We can use Fetch as an applicative" in { @@ -83,7 +83,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual 6).unsafeToFuture + io.map(_ shouldEqual 6).unsafeToFuture() } "We can traverse over a list with a Fetch for each element" in { @@ -95,7 +95,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(0, 1, 2)).unsafeToFuture + io.map(_ shouldEqual List(0, 1, 2)).unsafeToFuture() } "We can depend on previous computations of Fetch values" in { @@ -107,7 +107,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual 3).unsafeToFuture + io.map(_ shouldEqual 3).unsafeToFuture() } "We can collect a list of Fetch into one" in { @@ -116,7 +116,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(1, 2, 3)).unsafeToFuture + io.map(_ shouldEqual List(1, 2, 3)).unsafeToFuture() } "We can collect a list of Fetches with heterogeneous sources" in { @@ -125,7 +125,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(1, 2, 3, 4, 5)).unsafeToFuture + io.map(_ shouldEqual List(1, 2, 3, 4, 5)).unsafeToFuture() } "We can collect the results of a traversal" in { @@ -134,7 +134,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(1, 2, 3)).unsafeToFuture + io.map(_ shouldEqual List(1, 2, 3)).unsafeToFuture() } // Execution model @@ -151,7 +151,7 @@ class FetchTests extends FetchSpec { io.map({ case (log, result) => result shouldEqual (1, 2) log.rounds.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Traversals are implicitly batched" in { @@ -166,7 +166,7 @@ class FetchTests extends FetchSpec { io.map({ case (log, result) => result shouldEqual List(0, 1, 2) log.rounds.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Sequencing is implicitly batched" in { @@ -180,7 +180,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 3 totalBatches(log.rounds) shouldEqual 1 - }).unsafeToFuture + }).unsafeToFuture() } "Identities are deduped when batched" in { @@ -197,7 +197,7 @@ class FetchTests extends FetchSpec { log.rounds.head.queries.head.request should matchPattern { case Batch(NonEmptyList(1, List(2)), _) => } - }).unsafeToFuture + }).unsafeToFuture() } "The product of two fetches implies parallel fetching" in { @@ -210,7 +210,7 @@ class FetchTests extends FetchSpec { result shouldEqual (1, List(0, 1, 2)) log.rounds.size shouldEqual 1 log.rounds.head.queries.size shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Concurrent fetching calls batches only when it can" in { @@ -223,7 +223,7 @@ class FetchTests extends FetchSpec { result shouldEqual (1, List(0, 1, 2)) log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 0 - }).unsafeToFuture + }).unsafeToFuture() } "Concurrent fetching performs requests to multiple data sources in parallel" in { @@ -236,7 +236,7 @@ class FetchTests extends FetchSpec { result shouldEqual ((1, List(0, 1)), 3) log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 0 - }).unsafeToFuture + }).unsafeToFuture() } "The product of concurrent fetches implies everything fetched concurrently" in { @@ -256,7 +256,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 1 totalFetched(log.rounds) shouldEqual 4 - }).unsafeToFuture + }).unsafeToFuture() } "The product of concurrent fetches of the same type implies everything fetched in a single batch" in { @@ -286,7 +286,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 2 totalBatches(log.rounds) shouldEqual 2 totalFetched(log.rounds) shouldEqual 5 - }).unsafeToFuture + }).unsafeToFuture() } "Every level of joined concurrent fetches is combined and batched" in { @@ -312,7 +312,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 2 totalBatches(log.rounds) shouldEqual 2 totalFetched(log.rounds) shouldEqual 4 - }).unsafeToFuture + }).unsafeToFuture() } "Every level of sequenced concurrent fetches is batched" in { @@ -343,7 +343,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 3 totalBatches(log.rounds) shouldEqual 3 totalFetched(log.rounds) shouldEqual 9 + 4 + 6 - }).unsafeToFuture + }).unsafeToFuture() } "The product of two fetches from the same data source implies batching" in { @@ -356,7 +356,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 1 totalFetched(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Sequenced fetches are run concurrently" in { @@ -369,7 +369,7 @@ class FetchTests extends FetchSpec { result shouldEqual List(1, 2, 3, 4, 5) log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Sequenced fetches are deduped" in { @@ -383,7 +383,7 @@ class FetchTests extends FetchSpec { log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 1 totalFetched(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Traversals are batched" in { @@ -396,7 +396,7 @@ class FetchTests extends FetchSpec { result shouldEqual List(1, 2, 3) log.rounds.size shouldEqual 1 totalBatches(log.rounds) shouldEqual 1 - }).unsafeToFuture + }).unsafeToFuture() } "Duplicated sources are only fetched once" in { @@ -409,7 +409,7 @@ class FetchTests extends FetchSpec { result shouldEqual List(1, 2, 1) log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Sources that can be fetched concurrently inside a for comprehension will be" in { @@ -425,7 +425,7 @@ class FetchTests extends FetchSpec { result shouldEqual List(1, 2, 1) log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Pure Fetches allow to explore further in the Fetch" in { @@ -444,7 +444,7 @@ class FetchTests extends FetchSpec { result shouldEqual (1, 5) log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } // Caching @@ -467,7 +467,7 @@ class FetchTests extends FetchSpec { io.map({ case (log, result) => result shouldEqual 2 totalFetched(log.rounds) shouldEqual 3 - }).unsafeToFuture + }).unsafeToFuture() } "Batched elements are cached and thus not fetched more than once" in { @@ -489,7 +489,7 @@ class FetchTests extends FetchSpec { result shouldEqual 2 log.rounds.size shouldEqual 1 totalFetched(log.rounds) shouldEqual 3 - }).unsafeToFuture + }).unsafeToFuture() } "Elements that are cached won't be fetched" in { @@ -518,7 +518,7 @@ class FetchTests extends FetchSpec { result shouldEqual 2 totalFetched(log.rounds) shouldEqual 0 log.rounds.size shouldEqual 0 - }).unsafeToFuture + }).unsafeToFuture() } "Fetch#run accepts a cache as the second (optional) parameter" in { @@ -543,7 +543,7 @@ class FetchTests extends FetchSpec { val io = Fetch.run[IO](fetch, cache) - io.map(_ shouldEqual 2).unsafeToFuture + io.map(_ shouldEqual 2).unsafeToFuture() } "Fetch#runCache accepts a cache as the second (optional) parameter" in { @@ -570,7 +570,7 @@ class FetchTests extends FetchSpec { io.map({ case (c, result) => result shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } "Fetch#runCache works without the optional cache parameter" in { @@ -590,7 +590,7 @@ class FetchTests extends FetchSpec { io.map({ case (c, result) => result shouldEqual 2 - }).unsafeToFuture + }).unsafeToFuture() } case class ForgetfulCache[F[_]: Monad]() extends DataCache[F] { @@ -621,7 +621,7 @@ class FetchTests extends FetchSpec { result shouldEqual 2 log.rounds.size shouldEqual 7 totalFetched(log.rounds) shouldEqual 7 - }).unsafeToFuture + }).unsafeToFuture() } "We can use a custom cache that discards elements together with concurrent fetches" in { @@ -643,7 +643,7 @@ class FetchTests extends FetchSpec { result shouldEqual 2 log.rounds.size shouldEqual 8 totalFetched(log.rounds) shouldEqual 10 - }).unsafeToFuture + }).unsafeToFuture() } // Errors @@ -654,7 +654,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(MissingIdentity(Never(), _, _)) => }) - .unsafeToFuture + .unsafeToFuture() } "Data sources with errors throw fetch failures that can be handled" in { @@ -662,7 +662,7 @@ class FetchTests extends FetchSpec { io.handleErrorWith(err => IO.pure(42)) .map(_ shouldEqual 42) - .unsafeToFuture + .unsafeToFuture() } "Data sources with errors won't fail if they're cached" in { @@ -672,7 +672,7 @@ class FetchTests extends FetchSpec { ) val io = Fetch.run[IO](never, cache) - io.map(_ shouldEqual 1).unsafeToFuture + io.map(_ shouldEqual 1).unsafeToFuture() } def fetchError[F[_]: ConcurrentEffect]: Fetch[F, Int] = @@ -684,7 +684,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(UnhandledException(AnException(), _)) => }) - .unsafeToFuture + .unsafeToFuture() } "We can lift handle and recover from errors in Fetch" in { @@ -692,7 +692,7 @@ class FetchTests extends FetchSpec { io.handleErrorWith(err => IO.pure(42)) .map(_ shouldEqual 42) - .unsafeToFuture + .unsafeToFuture() } "If a fetch fails in the left hand of a product the product will fail" in { @@ -704,7 +704,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(UnhandledException(AnException(), _)) => }) - .unsafeToFuture + .unsafeToFuture() } "If a fetch fails in the right hand of a product the product will fail" in { @@ -716,7 +716,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(UnhandledException(AnException(), _)) => }) - .unsafeToFuture + .unsafeToFuture() } "If there is a missing identity in the left hand of a product the product will fail" in { @@ -728,7 +728,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(MissingIdentity(Never(), _, _)) => }) - .unsafeToFuture + .unsafeToFuture() } "If there is a missing identity in the right hand of a product the product will fail" in { @@ -740,7 +740,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(MissingIdentity(Never(), _, _)) => }) - .unsafeToFuture + .unsafeToFuture() } "If there are multiple failing identities the fetch will fail" in { @@ -752,7 +752,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(MissingIdentity(Never(), _, _)) => }) - .unsafeToFuture + .unsafeToFuture() } // Optional fetches @@ -762,7 +762,7 @@ class FetchTests extends FetchSpec { object MaybeMissing extends Data[MaybeMissing, Int] { def name = "Maybe Missing" - implicit def source[F[_]: ConcurrentEffect] = + implicit def source[F[_]: ConcurrentEffect]: DataSource[F, MaybeMissing, Int] = new DataSource[F, MaybeMissing, Int] { override def data = MaybeMissing @@ -784,14 +784,14 @@ class FetchTests extends FetchSpec { def fetch[F[_]: ConcurrentEffect]: Fetch[F, Option[Int]] = maybeOpt(1) - Fetch.run[IO](fetch).map(_ shouldEqual Some(1)).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual Some(1)).unsafeToFuture() } "We can run optional fetches with traverse" in { def fetch[F[_]: ConcurrentEffect]: Fetch[F, List[Int]] = List(1, 2, 3).traverse(maybeOpt[F]).map(_.flatten) - Fetch.run[IO](fetch).map(_ shouldEqual List(1, 3)).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual List(1, 3)).unsafeToFuture() } "We can run optional fetches with other data sources" in { @@ -801,7 +801,7 @@ class FetchTests extends FetchSpec { (ones, maybes).mapN { case (os, ms) => os ++ ms.flatten } } - Fetch.run[IO](fetch).map(_ shouldEqual List(1, 2, 3, 1, 3)).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual List(1, 2, 3, 1, 3)).unsafeToFuture() } "We can make fetches that depend on optional fetch results when they aren't defined" in { @@ -811,7 +811,7 @@ class FetchTests extends FetchSpec { result <- maybe.fold(Fetch.pure[F, Int](42))(i => one(i)) } yield result - Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture() } "We can make fetches that depend on optional fetch results when they are defined" in { @@ -821,7 +821,7 @@ class FetchTests extends FetchSpec { result <- maybe.fold(Fetch.pure[F, Int](42))(i => one(i)) } yield result - Fetch.run[IO](fetch).map(_ shouldEqual 1).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 1).unsafeToFuture() } // IO in Fetch @@ -830,7 +830,7 @@ class FetchTests extends FetchSpec { def fetch[F[_]: ConcurrentEffect]: Fetch[F, Int] = Fetch.liftIO(IO(42)) - Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture() } "A failed IO action lifted into Fetch will cause a Fetch to fail" in { @@ -842,20 +842,21 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(UnhandledException(AnException(), _)) => }) - .unsafeToFuture + .unsafeToFuture() } "A IO action can be combined with data fetches" in { def fetch[F[_]: ConcurrentEffect]: Fetch[F, List[Int]] = for { - x <- Fetch.liftIO(IO(3)) - manies <- many(x) - (ones, y) <- (manies.traverse(one[F]), Fetch.liftIO(IO(42))).tupled + x <- Fetch.liftIO(IO(3)) + manies <- many(x) + onesAndY <- (manies.traverse(one[F]), Fetch.liftIO(IO(42))).tupled + (ones, y) = onesAndY } yield ones :+ y val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(0, 1, 2, 42)).unsafeToFuture + io.map(_ shouldEqual List(0, 1, 2, 42)).unsafeToFuture() } // Concurrent[_] in Fetch @@ -867,7 +868,7 @@ class FetchTests extends FetchSpec { def fetch[F[_]: ConcurrentEffect]: Fetch[F, Int] = Fetch.liftF(fortyTwo) - Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture + Fetch.run[IO](fetch).map(_ shouldEqual 42).unsafeToFuture() } "A failed Concurrent action lifted into Fetch will cause a Fetch to fail" in { @@ -882,7 +883,7 @@ class FetchTests extends FetchSpec { io.attempt .map(_ should matchPattern { case Left(UnhandledException(AnException(), _)) => }) - .unsafeToFuture + .unsafeToFuture() } "A Concurrent action can be combined with data fetches" in { @@ -891,13 +892,14 @@ class FetchTests extends FetchSpec { def fetch[F[_]: ConcurrentEffect]: Fetch[F, List[Int]] = for { - x <- Fetch.liftF(concurrently(3)) - manies <- many(x) - (ones, y) <- (manies.traverse(one[F]), Fetch.liftF(concurrently(42))).tupled + x <- Fetch.liftF(concurrently(3)) + manies <- many(x) + onesAndY <- (manies.traverse(one[F]), Fetch.liftF(concurrently(42))).tupled + (ones, y) = onesAndY } yield ones :+ y val io = Fetch.run[IO](fetch) - io.map(_ shouldEqual List(0, 1, 2, 42)).unsafeToFuture + io.map(_ shouldEqual List(0, 1, 2, 42)).unsafeToFuture() } } diff --git a/project/ProjectPlugin.scala b/project/ProjectPlugin.scala index 4aea71e1..7851734e 100644 --- a/project/ProjectPlugin.scala +++ b/project/ProjectPlugin.scala @@ -61,9 +61,9 @@ object ProjectPlugin extends AutoPlugin { lazy val examplesSettings = Seq( libraryDependencies ++= Seq( - "io.circe" %% "circe-generic" % "0.13.0", - "org.tpolecat" %% "doobie-core" % "0.12.1", - "org.tpolecat" %% "doobie-h2" % "0.12.1", + "io.circe" %% "circe-generic" % "0.14.0-M3", + "org.tpolecat" %% "doobie-core" % "0.11.0-M2", + "org.tpolecat" %% "doobie-h2" % "0.11.0-M2", "org.tpolecat" %% "atto-core" % "0.9.3", "org.http4s" %% "http4s-blaze-client" % "0.21.21", "org.http4s" %% "http4s-circe" % "0.21.21", @@ -82,8 +82,16 @@ object ProjectPlugin extends AutoPlugin { case _ => withStripedLinter }) :+ "-language:higherKinds" }, - addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full), - addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + libraryDependencies ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((3, 0)) => Seq() + case _ => + Seq( + compilerPlugin("org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full), + compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") + ) + } + }, scalacOptions := Seq( "-unchecked", "-deprecation", @@ -93,8 +101,9 @@ object ProjectPlugin extends AutoPlugin { "-language:existentials", "-language:postfixOps" ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => Seq() - case _ => Seq("-Ypartial-unification") + case Some((3, 0)) => Seq("-source:3.0-migration", "-Ykind-projector") + case Some((2, 13)) => Seq("-Ywarn-dead-code") + case _ => Seq("-Ywarn-dead-code", "-Ypartial-unification") }) ) diff --git a/project/plugins.sbt b/project/plugins.sbt index cf3242b5..9b7f968b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,12 +3,11 @@ addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.19") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.6.0") -addSbtPlugin("com.alejandrohdezma" %% "sbt-codecov" % "0.2.0") addSbtPlugin("com.alejandrohdezma" %% "sbt-github" % "0.9.2") addSbtPlugin("com.alejandrohdezma" % "sbt-github-header" % "0.9.2") addSbtPlugin("com.alejandrohdezma" % "sbt-github-mdoc" % "0.9.2") addSbtPlugin("com.alejandrohdezma" % "sbt-mdoc-toc" % "0.3.0") addSbtPlugin("com.alejandrohdezma" % "sbt-remove-test-from-pom" % "0.1.0") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")