Skip to content

Commit

Permalink
Enable fatal warnings and set Scala 2.13 as default
Browse files Browse the repository at this point in the history
 - Replace usage of deprecated methods
 - Use ScalaTest `inside` instead of inexhaustive pattern matching
  • Loading branch information
joroKr21 committed Sep 22, 2022
1 parent 10b1cc9 commit 2fa3303
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 115 deletions.
16 changes: 7 additions & 9 deletions build.sbt
Expand Up @@ -3,7 +3,7 @@ import microsites.ExtraMdFileConfig

lazy val buildSettings = Seq(
organization := "com.github.finagle",
scalaVersion := "2.12.15",
scalaVersion := "2.13.8",
crossScalaVersions := Seq("2.12.15", "2.13.8")
)

Expand All @@ -30,7 +30,8 @@ def compilerOptions(scalaVersion: String): Seq[String] = Seq(
"-unchecked",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Xlint"
"-Xlint",
"-Xfatal-warnings"
) ++ (CrossVersion.partialVersion(scalaVersion) match {
case Some((2, scalaMajor)) if scalaMajor == 12 => scala212CompilerOptions
case Some((2, scalaMajor)) if scalaMajor == 13 => scala213CompilerOptions
Expand All @@ -43,7 +44,8 @@ lazy val scala212CompilerOptions = Seq(
)

lazy val scala213CompilerOptions = Seq(
"-Wunused:imports"
"-Wunused:imports",
"-Xlint:-byname-implicit"
)

val testDependencies = Seq(
Expand All @@ -66,9 +68,7 @@ val baseSettings = Seq(
resolvers ++= Resolver.sonatypeOssRepos("releases"),
resolvers ++= Resolver.sonatypeOssRepos("snapshots"),
scalacOptions ++= compilerOptions(scalaVersion.value),
(Compile / console / scalacOptions) ~= {
_.filterNot(Set("-Ywarn-unused-import"))
},
(Compile / console / scalacOptions) ~= (_.filterNot(Set("-Ywarn-unused-import"))),
(Compile / console / scalacOptions) += "-Yrepl-class-based",
(Test / fork) := true,
(ThisBuild / javaOptions) ++= Seq("-Xss2048K"),
Expand Down Expand Up @@ -219,9 +219,7 @@ lazy val docSettings = allSettings ++ Seq(
"-doc-root-content",
((Compile / resourceDirectory).value / "rootdoc.txt").getAbsolutePath
),
scalacOptions ~= {
_.filterNot(Set("-Yno-predef", "-Xlint", "-Ywarn-unused-import"))
},
scalacOptions ~= (_.filterNot(Set("-Yno-predef", "-Xlint", "-Ywarn-unused-import"))),
git.remoteRepo := "git@github.com:finagle/finch.git",
(ScalaUnidoc / unidoc / unidocProjectFilter) := inAnyProject -- inProjects(benchmarks, jsonTest),
(makeSite / includeFilter) := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.svg" | "*.js" | "*.swf" | "*.yml" | "*.md",
Expand Down
2 changes: 1 addition & 1 deletion circe/src/test/scala/io/finch/circe/test/CirceSpec.scala
Expand Up @@ -11,7 +11,7 @@ class CirceSpec extends AbstractJsonSpec {
checkJson("circe")
checkStreamJson[Enumerator, IO]("circe-iteratee")(Enumerator.enumList, _.toVector.unsafeRunSync().toList)
checkStreamJson[Stream, IO]("circe-fs2")(
list => Stream.fromIterator[IO](list.toIterator, 1024),
list => Stream.fromIterator[IO](list.iterator, 1024),
_.compile.toList.unsafeRunSync()
)
}
Expand Down
12 changes: 4 additions & 8 deletions core/src/main/scala/io/finch/DecodeEntity.scala
Expand Up @@ -58,17 +58,13 @@ trait HighPriorityDecode extends LowPriorityDecode {

trait LowPriorityDecode {

/** Creates an [[DecodeEntity]] instance from a given function `String => Either[Throwable, A]`.
*/
def instance[A](fn: String => Either[Throwable, A]): DecodeEntity[A] = new DecodeEntity[A] {
def apply(s: String): Either[Throwable, A] = fn(s)
}
/** Creates an [[DecodeEntity]] instance from a given function `String => Either[Throwable, A]`. */
def instance[A](fn: String => Either[Throwable, A]): DecodeEntity[A] = fn(_)

/** Creates a [[Decode]] from [[shapeless.Generic]] for single value case classes.
*/
/** Creates a [[Decode]] from [[shapeless.Generic]] for single value case classes. */
implicit def decodeFromGeneric[A, H <: HList, E](implicit
gen: Generic.Aux[A, H],
ev: (E :: HNil) =:= H,
de: DecodeEntity[E]
): DecodeEntity[A] = instance(s => de(s).right.map(b => gen.from(b :: HNil)))
): DecodeEntity[A] = instance(de(_).map(b => gen.from(b :: HNil)))
}
3 changes: 1 addition & 2 deletions core/src/main/scala/io/finch/Error.scala
Expand Up @@ -3,7 +3,6 @@ package io.finch
import cats.data.{NonEmptyChain, NonEmptyList}
import cats.{Eq, Semigroup, Show}

import scala.compat.Platform.EOL
import scala.reflect.ClassTag
import scala.util.control.NoStackTrace

Expand All @@ -25,7 +24,7 @@ sealed abstract class Error extends Exception with NoStackTrace
final case class Errors(errors: NonEmptyChain[Error]) extends Exception with NoStackTrace {
override def getMessage: String =
"One or more errors reading request:" +
errors.iterator.map(_.getMessage).mkString(EOL + " ", EOL + " ", "")
errors.iterator.map(_.getMessage).mkString(System.lineSeparator + " ", System.lineSeparator + " ", "")
}

object Errors {
Expand Down
7 changes: 3 additions & 4 deletions core/src/main/scala/io/finch/endpoint/multipart.scala
Expand Up @@ -3,6 +3,7 @@ package io.finch.endpoint
import cats.Id
import cats.data.NonEmptyList
import cats.effect.Sync
import cats.syntax.all._
import com.twitter.finagle.http.Request
import com.twitter.finagle.http.exp.Multipart.{FileUpload => FinagleFileUpload}
import com.twitter.finagle.http.exp.{Multipart => FinagleMultipart, MultipartDecoder}
Expand Down Expand Up @@ -35,11 +36,9 @@ abstract private[finch] class Attribute[F[_]: Sync, G[_], A](val name: String)(i
all(input) match {
case None => missing(name)
case Some(values) =>
val decoded = values.map(d.apply)
val errors = decoded.collect { case Left(t) => t }

val (errors, decoded) = values.toList.map(d.apply).separate
NonEmptyList.fromList(errors) match {
case None => present(decoded.map(_.right.get))
case None => present(NonEmptyList.fromListUnsafe(decoded))
case Some(es) => unparsed(es, tag)
}
}
Expand Down
11 changes: 4 additions & 7 deletions core/src/main/scala/io/finch/endpoint/param.scala
Expand Up @@ -3,6 +3,7 @@ package io.finch.endpoint
import cats.Id
import cats.data.NonEmptyList
import cats.effect.Sync
import cats.syntax.all._
import io.finch._

import scala.reflect.ClassTag
Expand Down Expand Up @@ -62,14 +63,10 @@ abstract private[finch] class Params[F[_], G[_], A](name: String)(implicit
input.request.params.getAll(name) match {
case value if value.isEmpty => missing(name)
case value =>
val decoded = value.map(d.apply).toList
val errors = decoded.collect { case Left(t) => t }

val (errors, decoded) = value.toList.map(d.apply).separate
NonEmptyList.fromList(errors) match {
case None =>
F.pure(Output.payload(present(decoded.map(_.right.get))))
case Some(es) =>
F.raiseError(Errors(es.map(t => Error.ParamNotParsed(name, tag).initCause(t).asInstanceOf[Error])))
case None => F.pure(Output.payload(present(decoded)))
case Some(es) => F.raiseError(Errors(es.map(Error.ParamNotParsed(name, tag).initCause(_).asInstanceOf[Error])))
}
}
}
Expand Down
10 changes: 4 additions & 6 deletions core/src/test/scala/io/finch/BodySpec.scala
Expand Up @@ -76,18 +76,16 @@ class BodySpec extends FinchSpec {
val plain = Input.post("/").withBody[Text.Plain](f)
val csv = Input.post("/").withBody[Application.Csv](f)
val endpoint = body[Foo, Text.Plain :+: Application.Csv :+: CNil]

endpoint(plain).awaitValueUnsafe(dispatcherIO) === Some(f) && endpoint(csv).awaitValueUnsafe(dispatcherIO) === Some(f)
}
}

it should "resolve into NotParsed(Decode.UMTE) if Content-Type does not match" in {
val i = Input.post("/").withBody[Application.Xml](Buf.Utf8("foo"))
val b = body[Foo, Text.Plain :+: Application.Csv :+: CNil]
val Some(Left(error)) = b(i).awaitOutput(dispatcherIO)

error shouldBe a[Error.NotParsed]
error.getCause shouldBe Decode.UnsupportedMediaTypeException

inside(b(i).awaitOutput(dispatcherIO)) { case Some(Left(error)) =>
error shouldBe a[Error.NotParsed]
error.getCause shouldBe Decode.UnsupportedMediaTypeException
}
}
}
54 changes: 25 additions & 29 deletions core/src/test/scala/io/finch/BootstrapSpec.scala
Expand Up @@ -19,26 +19,27 @@ class BootstrapSpec extends FinchSpec {
it should "handle both Error and Errors" in {
check { e: Either[Error, Errors] =>
val exception = e.fold[Exception](identity, identity)

val ee = Endpoint[IO].liftAsync[Unit](IO.raiseError(exception))
val (_, Right(rep)) = bootstrap.serve[Text.Plain](ee).compile.apply(Request()).unsafeRunSync()
rep.status === Status.BadRequest
inside(bootstrap.serve[Text.Plain](ee).compile.apply(Request()).unsafeRunSync()) { case (_, Right(rep)) =>
rep.status === Status.BadRequest
}
}
}

it should "catch custom exceptions in attempt" in {
val exception = new IllegalStateException
val endpoint = Endpoint[IO].liftAsync[Unit](IO.raiseError(exception))
val (_, Left(e)) = bootstrap.serve[Text.Plain](endpoint).compile.apply(Request()).unsafeRunSync()
e shouldBe exception
inside(bootstrap.serve[Text.Plain](endpoint).compile.apply(Request()).unsafeRunSync()) { case (_, Left(e)) =>
e shouldBe exception
}
}

it should "respond 404 if endpoint is not matched" in {
check { req: Request =>
val s = bootstrap.serve[Text.Plain](Endpoint[IO].empty[Unit]).compile
val (_, Right(rep)) = s(req).unsafeRunSync()

rep.status === Status.NotFound
inside(s(req).unsafeRunSync()) { case (_, Right(rep)) =>
rep.status === Status.NotFound
}
}
}

Expand All @@ -54,12 +55,10 @@ class BootstrapSpec extends FinchSpec {
val cc = Request(Method.Post, "/foo")
val dd = Request(Method.Delete, "/foo")

def response(req: Request): Response = {
val (_, Right(res)) = s(req).unsafeRunSync()
res
}
response(Request(Method.Get, "/bar")).status shouldBe Status.NotFound
def response(req: Request): Response =
s(req).unsafeRunSync()._2.toTry.get

response(Request(Method.Get, "/bar")).status shouldBe Status.NotFound
response(aa).contentString shouldBe "get foo"
response(bb).contentString shouldBe "put foo"
response(cc).contentString shouldBe "post foo"
Expand All @@ -72,19 +71,18 @@ class BootstrapSpec extends FinchSpec {
it should "respond 415 if media type is not supported" in {
val b = body[Foo, Text.Plain]
val s = bootstrap.configure(enableUnsupportedMediaType = true).serve[Text.Plain](b).compile

val i = Input.post("/").withBody[Application.Csv](Foo("bar"))

val (_, Right(res)) = s(i.request).unsafeRunSync()
res.status shouldBe Status.UnsupportedMediaType
inside(s(i.request).unsafeRunSync()) { case (_, Right(res)) =>
res.status shouldBe Status.UnsupportedMediaType
}
}

it should "match the request version" in {
check { req: Request =>
val s = bootstrap.serve[Text.Plain](Endpoint[IO].const(())).compile
val (_, Right(rep)) = s(req).unsafeRunSync()

rep.version === req.version
inside(s(req).unsafeRunSync()) { case (_, Right(rep)) =>
rep.version === req.version
}
}
}

Expand All @@ -94,21 +92,19 @@ class BootstrapSpec extends FinchSpec {

check { (req: Request, include: Boolean) =>
val s = bootstrap.configure(includeDateHeader = include).serve[Text.Plain](Endpoint[IO].const(())).compile

val (_, Right(rep)) = s(req).unsafeRunSync()
val now = parseDate(currentTime())

(include && (parseDate(rep.date.get) - now).abs <= 1) || (!include && rep.date.isEmpty)
inside(s(req).unsafeRunSync()) { case (_, Right(rep)) =>
val now = parseDate(currentTime())
(include && (parseDate(rep.date.get) - now).abs <= 1) || (!include && rep.date.isEmpty)
}
}
}

it should "include Server header" in {
check { (req: Request, include: Boolean) =>
val s = bootstrap.configure(includeServerHeader = include).serve[Text.Plain](Endpoint[IO].const(())).compile

val (_, Right(rep)) = s(req).unsafeRunSync()

(include && rep.server === Some("Finch")) || (!include && rep.server.isEmpty)
inside(s(req).unsafeRunSync()) { case (_, Right(rep)) =>
(include && rep.server === Some("Finch")) || (!include && rep.server.isEmpty)
}
}
}

Expand Down
16 changes: 10 additions & 6 deletions core/src/test/scala/io/finch/EndpointSpec.scala
Expand Up @@ -275,7 +275,7 @@ class EndpointSpec extends FinchSpec {
paramsNel("foor").map(_.toList.mkString),
binaryBody.map(new String(_)),
stringBody
).foreach(ii => ii(i).awaitValue(dispatcherIO).get.left.get shouldBe an[Error.NotPresent])
).foreach(_.apply(i).awaitValue(dispatcherIO).flatMap(_.swap.toOption).get shouldBe an[Error.NotPresent])
}

it should "maps lazily to values" in {
Expand Down Expand Up @@ -337,12 +337,16 @@ class EndpointSpec extends FinchSpec {
val lr = left.product(right)
val rl = right.product(left)

val all = a.fold(Set(_), _.errors.iterator.toSet) | b.fold(Set(_), _.errors.iterator.toSet)
val Some(Left(first)) = lr(Input.get("/")).awaitValue(dispatcherIO)
val Some(Left(second)) = rl(Input.get("/")).awaitValue(dispatcherIO)
val all =
a.fold[Set[Error]](e => Set(e), es => es.errors.iterator.toSet) ++
b.fold[Set[Error]](e => Set(e), es => es.errors.iterator.toSet)

first.asInstanceOf[Errors].errors.iterator.toSet === all &&
second.asInstanceOf[Errors].errors.iterator.toSet === all
inside(
(lr(Input.get("/")).awaitValue(dispatcherIO), rl(Input.get("/")).awaitValue(dispatcherIO))
) { case (Some(Left(first)), Some(Left(second))) =>
first.asInstanceOf[Errors].errors.iterator.toSet === all &&
second.asInstanceOf[Errors].errors.iterator.toSet === all
}
}
}

Expand Down
26 changes: 10 additions & 16 deletions core/src/test/scala/io/finch/FinchSpec.scala
Expand Up @@ -4,10 +4,11 @@ import cats.Eq
import cats.data.NonEmptyList
import cats.effect.std.Dispatcher
import cats.effect.{IO, Sync}
import cats.instances.AllInstances
import cats.syntax.all._
import com.twitter.finagle.http._
import com.twitter.io.Buf
import org.scalacheck.{Arbitrary, Cogen, Gen}
import org.scalatest.Inside
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.Checkers
Expand All @@ -18,7 +19,7 @@ import java.nio.charset.{Charset, StandardCharsets}
import java.util.UUID
import scala.reflect.classTag

trait FinchSpec extends AnyFlatSpec with Matchers with Checkers with AllInstances with MissingInstances with Endpoint.Module[IO] {
trait FinchSpec extends AnyFlatSpec with Matchers with Checkers with Inside with MissingInstances with Endpoint.Module[IO] {

def checkAll(name: String, ruleSet: Laws#RuleSet): Unit =
for ((id, prop) <- ruleSet.all.properties)
Expand Down Expand Up @@ -291,36 +292,29 @@ trait FinchSpec extends AnyFlatSpec with Matchers with Checkers with AllInstance
implicit def eqEndpoint[F[_]: Sync, A: Eq](implicit dispatcher: Dispatcher[F]): Eq[Endpoint[F, A]] = new Eq[Endpoint[F, A]] {
private[this] def count: Int = 16

private[this] def await(result: Endpoint.Result[F, A]): Option[(Input, Either[Throwable, Output[A]])] = for {
private[this] def await(result: Endpoint.Result[F, A]) = for {
r <- result.remainder
o <- result.awaitOutput(dispatcher)
} yield (r, o)

private[this] def inputs: Stream[Input] = Stream
.continually(
Arbitrary.arbitrary[Input].sample
)
.flatten

override def eqv(x: Endpoint[F, A], y: Endpoint[F, A]): Boolean = inputs.take(count).forall { input =>
val resultX = await(x(input))
val resultY = await(y(input))
private[this] def inputs =
Iterator.continually(Arbitrary.arbitrary[Input].sample).flatten

Eq[Option[(Input, Either[Throwable, Output[A]])]].eqv(resultX, resultY)
}
override def eqv(x: Endpoint[F, A], y: Endpoint[F, A]): Boolean =
inputs.take(count).forall(input => await(x(input)) eqv await(y(input)))
}

implicit def arbitraryEndpointResult[F[_]: Sync, A](implicit A: Arbitrary[A]): Arbitrary[EndpointResult[F, A]] =
Arbitrary(genEndpointResult[F, A])

implicit def eqEndpointResult[F[_]: Sync, A: Eq](implicit dispatcher: Dispatcher[F]): Eq[EndpointResult[F, A]] = new Eq[EndpointResult[F, A]] {
private[this] def await(result: Endpoint.Result[F, A]): Option[(Input, Either[Throwable, Output[A]])] = for {
private[this] def await(result: Endpoint.Result[F, A]) = for {
r <- result.remainder
o <- result.awaitOutput(dispatcher)
} yield (r, o)

def eqv(x: EndpointResult[F, A], y: EndpointResult[F, A]): Boolean =
Eq[Option[(Input, Either[Throwable, Output[A]])]].eqv(await(x), await(y))
await(x) eqv await(y)
}

implicit def arbitraryInput: Arbitrary[Input] =
Expand Down
4 changes: 1 addition & 3 deletions examples/src/main/scala/io/finch/iteratee/Main.scala
Expand Up @@ -47,14 +47,12 @@ object Main extends IOApp {

final case class IsPrime(isPrime: Boolean)

private def stream: Stream[Int] = Stream.continually(Random.nextInt())

val sumJson: Endpoint[IO, Result] = post("sumJson" :: jsonBodyStream[Enumerator, Number]) { enum: Enumerator[IO, Number] =>
enum.into(Iteratee.fold[IO, Number, Result](Result(0))(_ add _)).map(Ok)
}

val streamJson: Endpoint[IO, Enumerator[IO, Number]] = get("streamJson") {
Ok(Enumerator.enumStream[IO, Int](stream).map(Number.apply))
Ok(Enumerator.iterate[IO, Int](Random.nextInt())(_ => Random.nextInt()).map(Number.apply))
}

val isPrime: Endpoint[IO, Enumerator[IO, IsPrime]] =
Expand Down

0 comments on commit 2fa3303

Please sign in to comment.