From f1e7ed1c9b43d83138bc1edae4737785d676209b Mon Sep 17 00:00:00 2001 From: Sergey Kolbasov Date: Fri, 25 Sep 2020 16:08:44 +0300 Subject: [PATCH] Remove scalastyle in favor of scalafmt (#1267) --- .scalafix.conf | 7 + .scalafmt.conf | 11 + CONTRIBUTING.md | 4 +- .../scala/io/finch/argonaut/Decoders.scala | 10 +- .../scala/io/finch/argonaut/Encoders.scala | 4 +- .../scala/io/finch/argonaut/package.scala | 8 +- .../io/finch/argonaut/ArgonautSpec.scala | 2 +- .../src/main/scala/io/finch/benchmarks.scala | 17 +- .../src/main/scala/io/finch/data/Foo.scala | 2 +- build.sbt | 30 +- .../io/finch/circe/AccumulatingDecoders.scala | 28 +- .../scala/io/finch/circe/CirceError.scala | 3 +- .../main/scala/io/finch/circe/Decoders.scala | 31 +- .../main/scala/io/finch/circe/Encoders.scala | 7 +- .../main/scala/io/finch/circe/package.scala | 13 +- core/src/main/scala/io/finch/Accept.scala | 38 +- core/src/main/scala/io/finch/Bootstrap.scala | 87 +- core/src/main/scala/io/finch/Compile.scala | 68 +- core/src/main/scala/io/finch/Decode.scala | 40 +- .../main/scala/io/finch/DecodeEntity.scala | 40 +- core/src/main/scala/io/finch/DecodePath.scala | 15 +- .../main/scala/io/finch/DecodeStream.scala | 9 +- core/src/main/scala/io/finch/Encode.scala | 15 +- .../main/scala/io/finch/EncodeStream.scala | 13 +- core/src/main/scala/io/finch/Endpoint.scala | 962 +++++++++--------- .../main/scala/io/finch/EndpointModule.scala | 354 +++---- .../main/scala/io/finch/EndpointResult.scala | 68 +- core/src/main/scala/io/finch/Error.scala | 80 +- core/src/main/scala/io/finch/Input.scala | 129 ++- core/src/main/scala/io/finch/Output.scala | 163 ++- core/src/main/scala/io/finch/Outputs.scala | 52 +- .../main/scala/io/finch/ServerSentEvent.scala | 14 +- core/src/main/scala/io/finch/ToResponse.scala | 53 +- core/src/main/scala/io/finch/ToService.scala | 21 +- core/src/main/scala/io/finch/Trace.scala | 71 +- .../main/scala/io/finch/ValidationRule.scala | 48 +- .../main/scala/io/finch/ValidationRules.scala | 17 +- .../main/scala/io/finch/endpoint/body.scala | 80 +- .../main/scala/io/finch/endpoint/cookie.scala | 6 +- .../scala/io/finch/endpoint/endpoint.scala | 12 +- .../main/scala/io/finch/endpoint/header.scala | 22 +- .../main/scala/io/finch/endpoint/method.scala | 11 +- .../scala/io/finch/endpoint/multipart.scala | 32 +- .../main/scala/io/finch/endpoint/param.scala | 28 +- .../main/scala/io/finch/endpoint/path.scala | 36 +- .../main/scala/io/finch/internal/Mapper.scala | 89 +- .../scala/io/finch/internal/PairJoin.scala | 20 +- .../scala/io/finch/internal/ToAsync.scala | 7 +- .../scala/io/finch/internal/currentTime.scala | 7 +- .../scala/io/finch/internal/newLine.scala | 17 +- .../scala/io/finch/internal/package.scala | 43 +- core/src/main/scala/io/finch/package.scala | 6 +- core/src/test/scala/io/finch/BodySpec.scala | 5 +- .../test/scala/io/finch/BootstrapSpec.scala | 33 +- .../scala/io/finch/DecodeEntityLaws.scala | 2 +- .../test/scala/io/finch/DecodePathLaws.scala | 2 +- core/src/test/scala/io/finch/EncodeLaws.scala | 2 +- .../test/scala/io/finch/EndToEndSpec.scala | 10 +- .../test/scala/io/finch/EndpointSpec.scala | 81 +- .../scala/io/finch/EntityEndpointLaws.scala | 11 +- .../io/finch/EvaluatingEndpointLaws.scala | 6 +- .../test/scala/io/finch/ExtractPathLaws.scala | 11 +- core/src/test/scala/io/finch/FinchSpec.scala | 223 ++-- core/src/test/scala/io/finch/HeaderSpec.scala | 8 +- core/src/test/scala/io/finch/InputSpec.scala | 18 +- core/src/test/scala/io/finch/MethodSpec.scala | 79 +- .../scala/io/finch/MissingInstances.scala | 11 +- .../test/scala/io/finch/MultipartSpec.scala | 42 +- core/src/test/scala/io/finch/OutputSpec.scala | 10 +- core/src/test/scala/io/finch/ParamSpec.scala | 6 +- .../scala/io/finch/ServerSentEventSpec.scala | 51 +- .../test/scala/io/finch/StreamingLaws.scala | 32 +- core/src/test/scala/io/finch/data/Foo.scala | 11 +- .../io/finch/internal/HttpContentSpec.scala | 3 +- .../io/finch/internal/HttpMessageSpec.scala | 5 +- .../io/finch/internal/ToEffectLaws.scala | 9 +- .../io/finch/internal/ToEffectSpec.scala | 5 +- .../io/finch/internal/TooFastStringSpec.scala | 2 +- .../src/main/scala/io/finch/div/Main.scala | 34 +- .../main/scala/io/finch/iteratee/Main.scala | 16 +- .../main/scala/io/finch/middleware/Main.scala | 11 +- .../src/main/scala/io/finch/todo/App.scala | 16 +- .../src/main/scala/io/finch/todo/Main.scala | 46 +- .../src/main/scala/io/finch/wrk/Finagle.scala | 22 +- .../src/main/scala/io/finch/wrk/Finch.scala | 22 +- .../src/main/scala/io/finch/wrk/Wrk.scala | 10 +- .../test/scala/io/finch/todo/TodoSpec.scala | 26 +- fs2/src/main/scala/io/finch/fs2/package.scala | 66 +- .../scala/io/finch/fs2/Fs2StreamingSpec.scala | 22 +- .../scala/io/finch/generic/FromParams.scala | 44 +- .../io/finch/generic/GenericDerivation.scala | 6 +- .../main/scala/io/finch/generic/package.scala | 7 +- .../finch/generic/DerivedEndpointLaws.scala | 10 +- .../scala/io/finch/generic/GenericSpec.scala | 9 +- .../scala/io/finch/iteratee/package.scala | 52 +- .../iteratee/IterateeStreamingSpec.scala | 11 +- .../io/finch/test/AbstractJsonSpec.scala | 41 +- .../main/scala/io/finch/test/JsonLaws.scala | 49 +- .../test/data/ExampleNestedCaseClass.scala | 13 +- project/plugins.sbt | 4 +- .../io/finch/refined/PredicateFailed.scala | 2 +- .../main/scala/io/finch/refined/package.scala | 17 +- .../refined/DecodeEntityRefinedSpec.scala | 1 - .../finch/refined/PredicateFailedSpec.scala | 2 +- scalastyle-config.xml | 85 -- .../finch/test/ServiceIntegrationSuite.scala | 37 +- .../scala/io/finch/test/ServiceSuite.scala | 35 +- 107 files changed, 2185 insertions(+), 2159 deletions(-) create mode 100644 .scalafix.conf create mode 100644 .scalafmt.conf delete mode 100644 scalastyle-config.xml diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 000000000..e13a68115 --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,7 @@ +OrganizeImports { + groupedImports = Keep + removeUnused = true +} +rules = [ + OrganizeImports +] diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000..ba81b0ccf --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,11 @@ +version = 2.7.2 +maxColumn = 160 +align.preset = some +rewrite.rules = [ + RedundantBraces, + RedundantParens, + SortModifiers, + PreferCurlyFors +] +assumeStandardLibraryStripMargin = true +optIn.breakChainOnFirstMethodDot = false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b6994050..99b046426 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,12 +17,12 @@ _contributing process_ looks as follows: Finch follows the [Effective Scala][1] code style guide. When in doubt, look around the codebase and see how it's done elsewhere. -* Code and comments should be formatted to a width no greater than 120 columns +* Code and comments should be formatted to a width no greater than 160 columns * Files should be exempt of trailing spaces * Each abstraction with corresponding implementations should live in its own Scala file, i.e `Endpoint.scala` * Each implicit conversion (if possible) should be defined in the corresponding companion object -That said, the Scala style checker `sbt scalastyle` should pass on the code. +That said, the Scala source code shall be formatted with `sbt fmt` ## Write Tests Finch uses both [ScalaTest][2] and [ScalaCheck][3] with the following settings: diff --git a/argonaut/src/main/scala/io/finch/argonaut/Decoders.scala b/argonaut/src/main/scala/io/finch/argonaut/Decoders.scala index af28a740b..2a475cc0d 100644 --- a/argonaut/src/main/scala/io/finch/argonaut/Decoders.scala +++ b/argonaut/src/main/scala/io/finch/argonaut/Decoders.scala @@ -1,7 +1,7 @@ package io.finch.argonaut -import argonaut._ import argonaut.DecodeJson +import argonaut._ import cats.syntax.either._ import io.finch._ import io.finch.internal.HttpContent @@ -9,13 +9,13 @@ import io.finch.internal.HttpContent trait Decoders { /** - * Maps Argonaut's [[DecodeJson]] to Finch's [[Decode]]. - */ + * Maps Argonaut's [[DecodeJson]] to Finch's [[Decode]]. + */ implicit def decodeArgonaut[A](implicit d: DecodeJson[A]): Decode.Json[A] = Decode.json { (b, cs) => Parse.parse(b.asString(cs)).flatMap(_.as[A].result.leftMap(_._1)) match { case Right(result) => Right(result) - case Left(error) => Left(new Exception(error)) + case Left(error) => Left(new Exception(error)) } - } + } } diff --git a/argonaut/src/main/scala/io/finch/argonaut/Encoders.scala b/argonaut/src/main/scala/io/finch/argonaut/Encoders.scala index 019f50f1a..800858c61 100644 --- a/argonaut/src/main/scala/io/finch/argonaut/Encoders.scala +++ b/argonaut/src/main/scala/io/finch/argonaut/Encoders.scala @@ -9,8 +9,8 @@ trait Encoders { protected def printer: PrettyParams /** - * Maps Argonaut's [[EncodeJson]] to Finch's [[Encode]]. - */ + * Maps Argonaut's [[EncodeJson]] to Finch's [[Encode]]. + */ implicit def encodeArgonaut[A](implicit e: EncodeJson[A]): Encode.Json[A] = Encode.json((a, cs) => Buf.ByteArray.Owned(printer.pretty(e.encode(a)).getBytes(cs.name))) } diff --git a/argonaut/src/main/scala/io/finch/argonaut/package.scala b/argonaut/src/main/scala/io/finch/argonaut/package.scala index 490f2fd14..474de999c 100644 --- a/argonaut/src/main/scala/io/finch/argonaut/package.scala +++ b/argonaut/src/main/scala/io/finch/argonaut/package.scala @@ -11,15 +11,15 @@ package object argonaut extends Encoders with Decoders { } /** - * Provides an implicit [[PrettyParams]] that preserves order of the JSON fields. - */ + * Provides an implicit [[PrettyParams]] that preserves order of the JSON fields. + */ object preserveOrder extends Encoders with Decoders { override protected val printer: PrettyParams = PrettyParams.nospace.copy(preserveOrder = true) } /** - * Provides an implicit [[PrettyParams]] that both preserves order of the JSON fields and drop null keys. - */ + * Provides an implicit [[PrettyParams]] that both preserves order of the JSON fields and drop null keys. + */ object preserveOrderAndDropNullKeys extends Encoders with Decoders { override protected val printer: PrettyParams = PrettyParams.nospace.copy(preserveOrder = true, dropNullKeys = true) } diff --git a/argonaut/src/test/scala/io/finch/argonaut/ArgonautSpec.scala b/argonaut/src/test/scala/io/finch/argonaut/ArgonautSpec.scala index c746068ab..491ece5c8 100644 --- a/argonaut/src/test/scala/io/finch/argonaut/ArgonautSpec.scala +++ b/argonaut/src/test/scala/io/finch/argonaut/ArgonautSpec.scala @@ -1,7 +1,7 @@ package io.finch.argonaut -import argonaut._ import argonaut.Argonaut._ +import argonaut._ import io.finch.test.AbstractJsonSpec import io.finch.test.data._ diff --git a/benchmarks/src/main/scala/io/finch/benchmarks.scala b/benchmarks/src/main/scala/io/finch/benchmarks.scala index 570b08c8a..508a62cc3 100644 --- a/benchmarks/src/main/scala/io/finch/benchmarks.scala +++ b/benchmarks/src/main/scala/io/finch/benchmarks.scala @@ -1,5 +1,8 @@ package io.finch +import java.nio.charset.{Charset, StandardCharsets} +import java.util.concurrent.{ThreadLocalRandom, TimeUnit} + import cats.effect.IO import com.twitter.finagle.Service import com.twitter.finagle.http.{Request, Response} @@ -8,8 +11,6 @@ import com.twitter.util.Await import io.circe.generic.auto._ import io.finch.circe._ import io.finch.data.Foo -import java.nio.charset.{Charset, StandardCharsets} -import java.util.concurrent.{ThreadLocalRandom, TimeUnit} import org.openjdk.jmh.annotations._ import shapeless._ @@ -184,14 +185,13 @@ class JsonBenchmark extends FinchBenchmark { @BenchmarkMode(Array(Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) abstract class BootstrapBenchmark[CT](init: Bootstrap[Id, HNil, HNil])(implicit - tsf: Compile[IO, Endpoint[IO, List[Foo]] :: HNil, CT :: HNil] + tsf: Compile[IO, Endpoint[IO, List[Foo]] :: HNil, CT :: HNil] ) extends FinchBenchmark { protected def issueRequest(): Request = Request() - private val foo: Service[Request, Response] = init - .serve[CT](Endpoint[IO].const(List.fill(128)(Foo(scala.util.Random.alphanumeric.take(10).mkString)))) - .toService + private val foo: Service[Request, Response] = + init.serve[CT](Endpoint[IO].const(List.fill(128)(Foo(scala.util.Random.alphanumeric.take(10).mkString)))).toService @Benchmark def foos: Response = Await.result(foo.apply(issueRequest())) @@ -201,8 +201,7 @@ class JsonBootstrapBenchmark extends BootstrapBenchmark[Application.Json](Bootst class TextBootstrapBenchmark extends BootstrapBenchmark[Text.Plain](Bootstrap) -class JsonAndTextNegotiatedBootstrapBenchmark extends BootstrapBenchmark[Application.Json :+: Text.Plain :+: CNil]( - Bootstrap) { +class JsonAndTextNegotiatedBootstrapBenchmark extends BootstrapBenchmark[Application.Json :+: Text.Plain :+: CNil](Bootstrap) { private val acceptValues: Array[String] = Array("application/json", "text/plain") @@ -243,6 +242,6 @@ class HttpMessageBenchmark extends FinchBenchmark { @Benchmark def slowCharset: Charset = req.charset match { case Some(cs) => Charset.forName(cs) - case None => StandardCharsets.UTF_8 + case None => StandardCharsets.UTF_8 } } diff --git a/benchmarks/src/main/scala/io/finch/data/Foo.scala b/benchmarks/src/main/scala/io/finch/data/Foo.scala index 64a9cc12b..6d36ade15 100644 --- a/benchmarks/src/main/scala/io/finch/data/Foo.scala +++ b/benchmarks/src/main/scala/io/finch/data/Foo.scala @@ -1,8 +1,8 @@ package io.finch.data import com.twitter.io.Buf -import io.finch.{Decode, DecodeEntity, Encode} import io.finch.internal.HttpContent +import io.finch.{Decode, DecodeEntity, Encode} case class Foo(s: String) diff --git a/build.sbt b/build.sbt index b45cfe086..cb1347125 100644 --- a/build.sbt +++ b/build.sbt @@ -17,9 +17,9 @@ lazy val argonautVersion = "6.3.1" lazy val iterateeVersion = "0.19.0" lazy val refinedVersion = "0.9.16" lazy val catsEffectVersion = "2.2.0" -lazy val fs2Version = "2.4.4" +lazy val fs2Version = "2.4.4" -def compilerOptions(scalaVersion: String) = Seq( +def compilerOptions(scalaVersion: String): Seq[String] = Seq( "-deprecation", "-encoding", "UTF-8", "-feature", @@ -36,7 +36,7 @@ def compilerOptions(scalaVersion: String) = Seq( }) lazy val scala212CompilerOptions = Seq( - "-Yno-adapted-args", + "-Yno-adapted-args", "-Ywarn-unused-import", "-Xfuture" ) @@ -65,13 +65,16 @@ val baseSettings = Seq( Resolver.sonatypeRepo("snapshots") ), scalacOptions ++= compilerOptions(scalaVersion.value), - scalacOptions in (Compile, console) ~= { + scalacOptions in(Compile, console) ~= { _.filterNot(Set("-Ywarn-unused-import")) }, - scalacOptions in (Compile, console) += "-Yrepl-class-based", + scalacOptions in(Compile, console) += "-Yrepl-class-based", fork in Test := true, javaOptions in ThisBuild ++= Seq("-Xss2048K"), - addCompilerPlugin("org.typelevel" % "kind-projector" % "0.10.3" cross CrossVersion.binary) + addCompilerPlugin("org.typelevel" % "kind-projector" % "0.10.3" cross CrossVersion.binary), + ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.1", + semanticdbEnabled := true, + semanticdbVersion := scalafixSemanticdb.revision ) def updateVersionInFile(selectVersion: sbtrelease.Versions => String): ReleaseStep = @@ -92,7 +95,8 @@ def updateVersionInFile(selectVersion: sbtrelease.Versions => String): ReleaseSt pattern.replaceAllIn(content, m => m.matched.replaceAllLiterally(m.subgroups.head, newVersion)) new PrintWriter(fileName) { - write(newContent); close() + write(newContent); + close() } val vcs = Project.extract(st).get(releaseVcs).get vcs.add(fileName).! @@ -109,7 +113,7 @@ lazy val publishSettings = Seq( if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") else - Some("releases" at nexus + "service/local/staging/deploy/maven2") + Some("releases" at nexus + "service/local/staging/deploy/maven2") }, publishArtifact in Test := false, pgpSecretRing := file("local.secring.gpg"), @@ -199,9 +203,9 @@ lazy val docSettings = allSettings ++ Seq( "gray-light" -> "#E5E6E5", "gray-lighter" -> "#F4F3F4", "white-color" -> "#FFFFFF"), - addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), micrositeDocumentationUrl), + addMappingsToSiteDir(mappings in(ScalaUnidoc, packageDoc), micrositeDocumentationUrl), ghpagesNoJekyll := false, - scalacOptions in (ScalaUnidoc, unidoc) ++= Seq( + scalacOptions in(ScalaUnidoc, unidoc) ++= Seq( "-groups", "-implicits", "-skip-packages", "scalaz", @@ -213,7 +217,7 @@ lazy val docSettings = allSettings ++ Seq( _.filterNot(Set("-Yno-predef", "-Xlint", "-Ywarn-unused-import")) }, git.remoteRepo := "git@github.com:finagle/finch.git", - unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(benchmarks, jsonTest), + unidocProjectFilter in(ScalaUnidoc, unidoc) := inAnyProject -- inProjects(benchmarks, jsonTest), includeFilter in makeSite := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.svg" | "*.js" | "*.swf" | "*.yml" | "*.md", siteSubdirName in ScalaUnidoc := "docs" ) @@ -397,6 +401,8 @@ lazy val benchmarks = project val validateCommands = List( "clean", "compile", + "scalafix --check", + "scalafmtCheckAll", "test:compile", "coverage", "test", @@ -404,3 +410,5 @@ val validateCommands = List( "coverageAggregate" ) addCommandAlias("validate", validateCommands.mkString(";", ";", "")) +addCommandAlias("fmt", "all compile:scalafix; all test:scalafix; scalafmtAll") + diff --git a/circe/src/main/scala/io/finch/circe/AccumulatingDecoders.scala b/circe/src/main/scala/io/finch/circe/AccumulatingDecoders.scala index 01997fec4..d1a861d01 100644 --- a/circe/src/main/scala/io/finch/circe/AccumulatingDecoders.scala +++ b/circe/src/main/scala/io/finch/circe/AccumulatingDecoders.scala @@ -7,8 +7,8 @@ import cats.data.Validated import io.circe._ import io.circe.iteratee._ import io.circe.jawn.{decodeAccumulating, decodeByteBufferAccumulating} -import io.finch.{Application, Decode, DecodeStream} import io.finch.internal.HttpContent +import io.finch.{Application, Decode, DecodeStream} import io.iteratee.{Enumeratee, Enumerator} trait AccumulatingDecoders { @@ -26,25 +26,25 @@ trait AccumulatingDecoders { attemptJson.fold[Either[Throwable, A]](nel => Left(Errors(nel)), Right.apply) } - implicit def iterateeCirceDecoder[F[_], A : Decoder](implicit - monadError: MonadError[F, Throwable] - ): DecodeStream.Json[Enumerator, F, A] = DecodeStream.instance[Enumerator, F, A, Application.Json]((enum, cs) => { - val parsed = cs match { - case StandardCharsets.UTF_8 => - enum.map(_.asByteArray).through(byteStreamParser[F]) - case _ => - enum.map(_.asString(cs)).through(stringStreamParser[F]) - } - parsed.through(decoderAccumulating[F, A]) - }) + implicit def iterateeCirceDecoder[F[_], A: Decoder](implicit + monadError: MonadError[F, Throwable] + ): DecodeStream.Json[Enumerator, F, A] = DecodeStream.instance[Enumerator, F, A, Application.Json] { (enum, cs) => + val parsed = cs match { + case StandardCharsets.UTF_8 => + enum.map(_.asByteArray).through(byteStreamParser[F]) + case _ => + enum.map(_.asString(cs)).through(stringStreamParser[F]) + } + parsed.through(decoderAccumulating[F, A]) + } private def decoderAccumulating[F[_], A](implicit - F: MonadError[F, Throwable], + F: MonadError[F, Throwable], decode: Decoder[A] ): Enumeratee[F, Json, A] = Enumeratee.flatMap(json => decode.decodeAccumulating(json.hcursor) match { case Validated.Invalid(errors) => Enumerator.liftM(F.raiseError(Errors(errors))) - case Validated.Valid(a) => Enumerator.enumOne(a) + case Validated.Valid(a) => Enumerator.enumOne(a) } ) diff --git a/circe/src/main/scala/io/finch/circe/CirceError.scala b/circe/src/main/scala/io/finch/circe/CirceError.scala index a2651b9c7..6871a3030 100644 --- a/circe/src/main/scala/io/finch/circe/CirceError.scala +++ b/circe/src/main/scala/io/finch/circe/CirceError.scala @@ -1,8 +1,9 @@ package io.finch.circe -import cats.syntax.show._ import scala.util.control.NoStackTrace +import cats.syntax.show._ + private class CirceError(cause: io.circe.Error) extends Exception with NoStackTrace { override def getMessage: String = cause.show } diff --git a/circe/src/main/scala/io/finch/circe/Decoders.scala b/circe/src/main/scala/io/finch/circe/Decoders.scala index 236ae7703..6523bfbcd 100644 --- a/circe/src/main/scala/io/finch/circe/Decoders.scala +++ b/circe/src/main/scala/io/finch/circe/Decoders.scala @@ -1,5 +1,7 @@ package io.finch.circe +import java.nio.charset.StandardCharsets + import cats.MonadError import cats.effect.Sync import fs2.{Chunk, Stream} @@ -7,32 +9,31 @@ import io.circe.Decoder import io.circe.fs2 import io.circe.iteratee import io.circe.jawn._ -import io.finch.{Application, Decode, DecodeStream} import io.finch.internal.HttpContent +import io.finch.{Application, Decode, DecodeStream} import io.iteratee.Enumerator -import java.nio.charset.StandardCharsets trait Decoders { /** - * Maps a Circe's [[Decoder]] to Finch's [[Decode]]. - */ + * Maps a Circe's [[Decoder]] to Finch's [[Decode]]. + */ implicit def decodeCirce[A: Decoder]: Decode.Json[A] = Decode.json { (b, cs) => val decoded = cs match { case StandardCharsets.UTF_8 => decodeByteBuffer[A](b.asByteBuffer) - case _ => decode[A](b.asString(cs)) + case _ => decode[A](b.asString(cs)) } decoded match { case Left(error) => Left(new CirceError(error)) - case right => right + case right => right } } implicit def enumerateCirce[F[_], A: Decoder](implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): DecodeStream.Json[Enumerator, F, A] = - DecodeStream.instance[Enumerator, F, A, Application.Json]((enum, cs) => { + DecodeStream.instance[Enumerator, F, A, Application.Json] { (enum, cs) => val parsed = cs match { case StandardCharsets.UTF_8 => enum.map(_.asByteArray).through(iteratee.byteStreamParser[F]) @@ -40,20 +41,16 @@ trait Decoders { enum.map(_.asString(cs)).through(iteratee.stringStreamParser[F]) } parsed.through(iteratee.decoder[F, A]) - }) + } implicit def fs2Circe[F[_]: Sync, A: Decoder]: DecodeStream.Json[Stream, F, A] = - DecodeStream.instance[Stream, F, A, Application.Json]((stream, cs) => { + DecodeStream.instance[Stream, F, A, Application.Json] { (stream, cs) => val parsed = cs match { case StandardCharsets.UTF_8 => - stream - .mapChunks(chunk => chunk.flatMap(buf => Chunk.array(buf.asByteArray))) - .through(fs2.byteStreamParser[F]) + stream.mapChunks(chunk => chunk.flatMap(buf => Chunk.array(buf.asByteArray))).through(fs2.byteStreamParser[F]) case _ => - stream - .map(_.asString(cs)) - .through(fs2.stringStreamParser[F]) + stream.map(_.asString(cs)).through(fs2.stringStreamParser[F]) } parsed.through(fs2.decoder[F, A]) - }) + } } diff --git a/circe/src/main/scala/io/finch/circe/Encoders.scala b/circe/src/main/scala/io/finch/circe/Encoders.scala index 36745e11c..c255d45bf 100644 --- a/circe/src/main/scala/io/finch/circe/Encoders.scala +++ b/circe/src/main/scala/io/finch/circe/Encoders.scala @@ -1,9 +1,10 @@ package io.finch.circe +import java.nio.charset.Charset + import com.twitter.io.Buf import io.circe._ import io.finch.Encode -import java.nio.charset.Charset trait Encoders { @@ -11,8 +12,8 @@ trait Encoders { Buf.ByteBuffer.Owned(Printer.noSpaces.printToByteBuffer(json, cs)) /** - * Maps Circe's [[Encoder]] to Finch's [[Encode]]. - */ + * Maps Circe's [[Encoder]] to Finch's [[Encode]]. + */ implicit def encodeCirce[A](implicit e: Encoder[A]): Encode.Json[A] = Encode.json((a, cs) => print(e(a), cs)) } diff --git a/circe/src/main/scala/io/finch/circe/package.scala b/circe/src/main/scala/io/finch/circe/package.scala index d44bb981b..5dd1707b7 100644 --- a/circe/src/main/scala/io/finch/circe/package.scala +++ b/circe/src/main/scala/io/finch/circe/package.scala @@ -1,14 +1,15 @@ package io.finch +import java.nio.charset.Charset + import com.twitter.io.Buf import io.circe.{Json, Printer} -import java.nio.charset.Charset package object circe extends Encoders with Decoders { /** - * Provides a [[Printer]] that drops null keys. - */ + * Provides a [[Printer]] that drops null keys. + */ object dropNullValues extends Encoders with Decoders { private[this] val printer: Printer = Printer.noSpaces.copy(dropNullValues = true) override protected def print(json: Json, cs: Charset): Buf = @@ -16,9 +17,9 @@ package object circe extends Encoders with Decoders { } /** - * Provides a [[Printer]] that uses a simple form of feedback-controller to predict the - * size of the printed message. - */ + * Provides a [[Printer]] that uses a simple form of feedback-controller to predict the + * size of the printed message. + */ object predictSize extends Encoders with Decoders { private[this] val printer: Printer = Printer.noSpaces.copy(predictSize = true) override protected def print(json: Json, cs: Charset): Buf = diff --git a/core/src/main/scala/io/finch/Accept.scala b/core/src/main/scala/io/finch/Accept.scala index 79253038d..ad4e94158 100644 --- a/core/src/main/scala/io/finch/Accept.scala +++ b/core/src/main/scala/io/finch/Accept.scala @@ -5,12 +5,12 @@ import java.util.Locale import shapeless.Witness /** - * Models an HTTP Accept header (see RFC2616, 14.1). - * - * @note This API doesn't validate the input primary/sub types. - * - * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - */ + * Models an HTTP Accept header (see RFC2616, 14.1). + * + * @note This API doesn't validate the input primary/sub types. + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ abstract class Accept { def primary: String def sub: String @@ -45,18 +45,19 @@ object Accept { implicit def fromWitness[CT <: String](implicit w: Witness.Aux[CT]): Matcher[CT] = { val slashIndex = w.value.indexOf(47) if (slashIndex == 0 || slashIndex == w.value.length) Empty.asInstanceOf[Matcher[CT]] - else new Matcher[CT] { - private val primary: String = w.value.substring(0, slashIndex).trim.toLowerCase(Locale.ENGLISH) - private val sub: String = w.value.substring(slashIndex + 1, w.value.length).trim.toLowerCase(Locale.ENGLISH) - def apply(a: Accept): Boolean = - (a.primary == "*" && a.sub == "*") || (a.primary == primary && (a.sub == sub || a.sub == "*")) - } + else + new Matcher[CT] { + private val primary: String = w.value.substring(0, slashIndex).trim.toLowerCase(Locale.ENGLISH) + private val sub: String = w.value.substring(slashIndex + 1, w.value.length).trim.toLowerCase(Locale.ENGLISH) + def apply(a: Accept): Boolean = + (a.primary == "*" && a.sub == "*") || (a.primary == primary && (a.sub == sub || a.sub == "*")) + } } } /** - * Parses an [[Accept]] instance from a given string. Returns `null` when not able to parse. - */ + * Parses an [[Accept]] instance from a given string. Returns `null` when not able to parse. + */ def fromString(s: String): Accept = { // Adopted from Java's MimeType's API. val slashIndex = s.indexOf(47) @@ -64,9 +65,10 @@ object Accept { val length = if (semIndex < 0) s.length else semIndex if (slashIndex < 0 || slashIndex >= length) Empty - else new Accept { - val primary: String = s.substring(0, slashIndex).trim.toLowerCase(Locale.ENGLISH) - val sub: String = s.substring(slashIndex + 1, length).trim.toLowerCase(Locale.ENGLISH) - } + else + new Accept { + val primary: String = s.substring(0, slashIndex).trim.toLowerCase(Locale.ENGLISH) + val sub: String = s.substring(slashIndex + 1, length).trim.toLowerCase(Locale.ENGLISH) + } } } diff --git a/core/src/main/scala/io/finch/Bootstrap.scala b/core/src/main/scala/io/finch/Bootstrap.scala index 6843a2c5e..c0971106a 100644 --- a/core/src/main/scala/io/finch/Bootstrap.scala +++ b/core/src/main/scala/io/finch/Bootstrap.scala @@ -6,42 +6,42 @@ import com.twitter.finagle.http.{Request, Response} import shapeless._ /** - * Bootstraps a Finagle HTTP service out of the collection of Finch endpoints. - * - * {{{ - * val api: Service[Request, Response] = Bootstrap - * .configure(negotiateContentType = true, enableMethodNotAllowed = true) - * .serve[Application.Json](getUser :+: postUser) - * .serve[Text.Plain](healthcheck) - * .toService - * }}} - * - * - * == Supported Configuration Options == - * - * - `includeDateHeader` (default: `true`): whether or not to include the Date header into - * each response (see RFC2616, section 14.18) - * - * - `includeServerHeader` (default: `true`): whether or not to include the Server header into - * each response (see RFC2616, section 14.38) - * - * - `enableMethodNotAllowed` (default: `false`): whether or not to enable 405 MethodNotAllowed HTTP - * response (see RFC2616, section 10.4.6) - * - * - `enableUnsupportedMediaType` (default: `false`) whether or not to enable 415 - * UnsupportedMediaType HTTP response (see RFC7231, section 6.5.13) - * - * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html - * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - * @see https://tools.ietf.org/html/rfc7231#section-6.5.13 - */ + * Bootstraps a Finagle HTTP service out of the collection of Finch endpoints. + * + * {{{ + * val api: Service[Request, Response] = Bootstrap + * .configure(negotiateContentType = true, enableMethodNotAllowed = true) + * .serve[Application.Json](getUser :+: postUser) + * .serve[Text.Plain](healthcheck) + * .toService + * }}} + * + * == Supported Configuration Options == + * + * - `includeDateHeader` (default: `true`): whether or not to include the Date header into + * each response (see RFC2616, section 14.18) + * + * - `includeServerHeader` (default: `true`): whether or not to include the Server header into + * each response (see RFC2616, section 14.38) + * + * - `enableMethodNotAllowed` (default: `false`): whether or not to enable 405 MethodNotAllowed HTTP + * response (see RFC2616, section 10.4.6) + * + * - `enableUnsupportedMediaType` (default: `false`) whether or not to enable 415 + * UnsupportedMediaType HTTP response (see RFC7231, section 6.5.13) + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * @see https://tools.ietf.org/html/rfc7231#section-6.5.13 + */ class Bootstrap[F[_], ES <: HList, CTS <: HList]( val endpoints: ES, val includeDateHeader: Boolean = true, val includeServerHeader: Boolean = true, val enableMethodNotAllowed: Boolean = false, - val enableUnsupportedMediaType: Boolean = false) { self => + val enableUnsupportedMediaType: Boolean = false +) { self => class Serve[CT] { def apply[FF[_], E](e: Endpoint[FF, E]): Bootstrap[FF, Endpoint[FF, E] :: ES, CT :: CTS] = @@ -52,13 +52,13 @@ class Bootstrap[F[_], ES <: HList, CTS <: HList]( enableMethodNotAllowed, enableUnsupportedMediaType ) - } + } def configure( - includeDateHeader: Boolean = self.includeDateHeader, - includeServerHeader: Boolean = self.includeServerHeader, - enableMethodNotAllowed: Boolean = self.enableMethodNotAllowed, - enableUnsupportedMediaType: Boolean = self.enableUnsupportedMediaType + includeDateHeader: Boolean = self.includeDateHeader, + includeServerHeader: Boolean = self.includeServerHeader, + enableMethodNotAllowed: Boolean = self.enableMethodNotAllowed, + enableUnsupportedMediaType: Boolean = self.enableUnsupportedMediaType ): Bootstrap[F, ES, CTS] = new Bootstrap[F, ES, CTS]( endpoints, includeDateHeader, @@ -88,10 +88,11 @@ class Bootstrap[F[_], ES <: HList, CTS <: HList]( final override def toString: String = s"Bootstrap($endpoints)" } -object Bootstrap extends Bootstrap[Id, HNil, HNil]( - endpoints = HNil, - includeDateHeader = true, - includeServerHeader = true, - enableMethodNotAllowed = false, - enableUnsupportedMediaType = false -) +object Bootstrap + extends Bootstrap[Id, HNil, HNil]( + endpoints = HNil, + includeDateHeader = true, + includeServerHeader = true, + enableMethodNotAllowed = false, + enableUnsupportedMediaType = false + ) diff --git a/core/src/main/scala/io/finch/Compile.scala b/core/src/main/scala/io/finch/Compile.scala index 9dafc5c71..294e95ba7 100644 --- a/core/src/main/scala/io/finch/Compile.scala +++ b/core/src/main/scala/io/finch/Compile.scala @@ -1,26 +1,27 @@ package io.finch -import cats.{Applicative, MonadError} +import scala.annotation.implicitNotFound + import cats.syntax.all._ +import cats.{Applicative, MonadError} import com.twitter.finagle.http.{Method, Request, Response, Status, Version} import io.finch.internal.currentTime -import scala.annotation.implicitNotFound import shapeless._ /** - * Compiles a given list of [[Endpoint]]s and their content-types into single [[Endpoint.Compiled]]. - * - * Guarantees to: - * - * - handle Finch's own errors (i.e., [[Error]] and [[Error]]) as 400s - * - copy requests's HTTP version onto a response - * - respond with 404 when an endpoint is not matched - * - respond with 405 when an endpoint is not matched because method wasn't allowed (serve back an `Allow` header) - * - include the date header on each response (unless disabled) - * - include the server header on each response (unless disabled) - */ + * Compiles a given list of [[Endpoint]]s and their content-types into single [[Endpoint.Compiled]]. + * + * Guarantees to: + * + * - handle Finch's own errors (i.e., [[Error]] and [[Error]]) as 400s + * - copy requests's HTTP version onto a response + * - respond with 404 when an endpoint is not matched + * - respond with 405 when an endpoint is not matched because method wasn't allowed (serve back an `Allow` header) + * - include the date header on each response (unless disabled) + * - include the server header on each response (unless disabled) + */ @implicitNotFound( -"""An Endpoint you're trying to compile is missing one or more encoders. + """An Endpoint you're trying to compile is missing one or more encoders. Make sure each endpoint in ${ES}, ${CTS} is one of the following: @@ -38,24 +39,24 @@ trait Compile[F[_], ES <: HList, CTS <: HList] { object Compile { /** - * HTTP options propagated from [[Bootstrap]]. - */ + * HTTP options propagated from [[Bootstrap]]. + */ final case class Options( - includeDateHeader: Boolean, - includeServerHeader: Boolean, - enableMethodNotAllowed: Boolean, - enableUnsupportedMediaType: Boolean + includeDateHeader: Boolean, + includeServerHeader: Boolean, + enableMethodNotAllowed: Boolean, + enableUnsupportedMediaType: Boolean ) /** - * HTTP context propagated between endpoints. - * - * - `wouldAllow`: when non-empty, indicates that the incoming method wasn't allowed/matched - */ + * HTTP context propagated between endpoints. + * + * - `wouldAllow`: when non-empty, indicates that the incoming method wasn't allowed/matched + */ final case class Context(wouldAllow: List[Method] = Nil) private val respond400: PartialFunction[Throwable, Output[Nothing]] = { - case e: io.finch.Error => Output.failure(e, Status.BadRequest) + case e: io.finch.Error => Output.failure(e, Status.BadRequest) case es: io.finch.Errors => Output.failure(es, Status.BadRequest) } @@ -97,11 +98,11 @@ object Compile { type IsNegotiable[C] = OrElse[C <:< Coproduct, DummyImplicit] implicit def hlistTS[F[_], A, EH <: Endpoint[F, A], ET <: HList, CTH, CTT <: HList](implicit - ntrA: ToResponse.Negotiable[F, A, CTH], - ntrE: ToResponse.Negotiable[F, Exception, CTH], - F: MonadError[F, Throwable], - tsT: Compile[F, ET, CTT], - isNegotiable: IsNegotiable[CTH] + ntrA: ToResponse.Negotiable[F, A, CTH], + ntrE: ToResponse.Negotiable[F, Exception, CTH], + F: MonadError[F, Throwable], + tsT: Compile[F, ET, CTT], + isNegotiable: IsNegotiable[CTH] ): Compile[F, Endpoint[F, A] :: ET, CTH :: CTT] = new Compile[F, Endpoint[F, A] :: ET, CTH :: CTT] { def apply(es: Endpoint[F, A] :: ET, opts: Options, ctx: Context): Endpoint.Compiled[F] = { val handler = if (opts.enableUnsupportedMediaType) respond415.orElse(respond400) else respond400 @@ -111,14 +112,9 @@ object Compile { Endpoint.Compiled[F] { req: Request => underlying(Input.fromRequest(req)) match { case EndpointResult.Matched(rem, trc, out) if rem.route.isEmpty => - val accept = if (negotiateContent) req.accept.map(a => Accept.fromString(a)).toList else Nil - F - .flatMap(out)(oa => oa.toResponse(F, ntrA(accept), ntrE(accept)) - .map(r => conformHttp(r, req.version, opts))) - .attempt - .map(e => trc -> e) + F.flatMap(out)(oa => oa.toResponse(F, ntrA(accept), ntrE(accept)).map(r => conformHttp(r, req.version, opts))).attempt.map(e => trc -> e) case EndpointResult.NotMatched.MethodNotAllowed(allowed) => tsT(es.tail, opts, ctx.copy(wouldAllow = ctx.wouldAllow ++ allowed))(req) diff --git a/core/src/main/scala/io/finch/Decode.scala b/core/src/main/scala/io/finch/Decode.scala index 1b0fb6be5..20fad8d79 100644 --- a/core/src/main/scala/io/finch/Decode.scala +++ b/core/src/main/scala/io/finch/Decode.scala @@ -1,14 +1,16 @@ package io.finch -import com.twitter.io.Buf import java.nio.charset.Charset + import scala.util.control.NoStackTrace + +import com.twitter.io.Buf import shapeless.{:+:, CNil, Coproduct, Witness} /** - * Decodes an HTTP payload represented as [[Buf]] (encoded with [[Charset]]) into - * an arbitrary type `A`. - */ + * Decodes an HTTP payload represented as [[Buf]] (encoded with [[Charset]]) into + * an arbitrary type `A`. + */ trait Decode[A] { type ContentType <: String @@ -18,9 +20,9 @@ trait Decode[A] { object Decode { /** - * Indicates that a payload can not be decoded with a given [[Decode]] instance (or a coproduct - * of instances). - */ + * Indicates that a payload can not be decoded with a given [[Decode]] instance (or a coproduct + * of instances). + */ object UnsupportedMediaTypeException extends Exception with NoStackTrace type Aux[A, CT <: String] = Decode[A] { type ContentType = CT } @@ -29,8 +31,8 @@ object Decode { type Text[A] = Aux[A, Text.Plain] /** - * Creates an instance for a given type. - */ + * Creates an instance for a given type. + */ def instance[A, CT <: String](fn: (Buf, Charset) => Either[Throwable, A]): Aux[A, CT] = new Decode[A] { type ContentType = CT def apply(b: Buf, cs: Charset): Either[Throwable, A] = fn(b, cs) @@ -43,14 +45,14 @@ object Decode { instance[A, Text.Plain](fn) /** - * Returns a [[Decode]] instance for a given type (with required content type). - */ + * Returns a [[Decode]] instance for a given type (with required content type). + */ @inline def apply[A, CT <: String](implicit d: Aux[A, CT]): Aux[A, CT] = d /** - * Abstracting over [[Decode]] to select a correct decoder according to the `Content-Type` header - * value. - */ + * Abstracting over [[Decode]] to select a correct decoder according to the `Content-Type` header + * value. + */ trait Dispatchable[A, CT] { def apply(ct: String, b: Buf, cs: Charset): Either[Throwable, A] } @@ -63,9 +65,9 @@ object Decode { } implicit def coproductToDispatchable[A, CTH <: String, CTT <: Coproduct](implicit - decode: Decode.Aux[A, CTH], - witness: Witness.Aux[CTH], - tail: Dispatchable[A, CTT] + decode: Decode.Aux[A, CTH], + witness: Witness.Aux[CTH], + tail: Dispatchable[A, CTT] ): Dispatchable[A, CTH :+: CTT] = new Dispatchable[A, CTH :+: CTT] { def apply(ct: String, b: Buf, cs: Charset): Either[Throwable, A] = if (ct.equalsIgnoreCase(witness.value)) decode(b, cs) @@ -73,8 +75,8 @@ object Decode { } implicit def singleToDispatchable[A, CT <: String](implicit - decode: Decode.Aux[A, CT], - witness: Witness.Aux[CT] + decode: Decode.Aux[A, CT], + witness: Witness.Aux[CT] ): Dispatchable[A, CT] = coproductToDispatchable[A, CT, CNil].asInstanceOf[Dispatchable[A, CT]] } } diff --git a/core/src/main/scala/io/finch/DecodeEntity.scala b/core/src/main/scala/io/finch/DecodeEntity.scala index 6c3e06791..cd01c6da4 100644 --- a/core/src/main/scala/io/finch/DecodeEntity.scala +++ b/core/src/main/scala/io/finch/DecodeEntity.scala @@ -5,9 +5,9 @@ import java.util.UUID import shapeless._ /** - * Decodes an HTTP entity (eg: header, query-string param) represented as UTF-8 `String` into - * an arbitrary type `A`. - */ + * Decodes an HTTP entity (eg: header, query-string param) represented as UTF-8 `String` into + * an arbitrary type `A`. + */ trait DecodeEntity[A] { def apply(s: String): Either[Throwable, A] } @@ -15,8 +15,8 @@ trait DecodeEntity[A] { object DecodeEntity extends HighPriorityDecode { /** - * Returns a [[DecodeEntity]] instance for a given type. - */ + * Returns a [[DecodeEntity]] instance for a given type. + */ @inline def apply[A](implicit d: DecodeEntity[A]): DecodeEntity[A] = d implicit val decodeString: DecodeEntity[String] = instance(s => Right(s)) @@ -26,23 +26,35 @@ object DecodeEntity extends HighPriorityDecode { trait HighPriorityDecode extends LowPriorityDecode { implicit val decodeInt: DecodeEntity[Int] = instance(s => - try { Right(s.toInt)} catch {case e: Throwable => Left(e)}) + try Right(s.toInt) + catch { case e: Throwable => Left(e) } + ) implicit val decodeLong: DecodeEntity[Long] = instance(s => - try { Right(s.toLong)} catch {case e: Throwable => Left(e)}) + try Right(s.toLong) + catch { case e: Throwable => Left(e) } + ) implicit val decodeFloat: DecodeEntity[Float] = instance(s => - try { Right(s.toFloat)} catch {case e: Throwable => Left(e)}) + try Right(s.toFloat) + catch { case e: Throwable => Left(e) } + ) implicit val decodeDouble: DecodeEntity[Double] = instance(s => - try { Right(s.toDouble)} catch {case e: Throwable => Left(e)}) + try Right(s.toDouble) + catch { case e: Throwable => Left(e) } + ) implicit val decodeBoolean: DecodeEntity[Boolean] = instance(s => - try { Right(s.toBoolean)} catch {case e: Throwable => Left(e)}) + try Right(s.toBoolean) + catch { case e: Throwable => Left(e) } + ) implicit val decodeUUID: DecodeEntity[UUID] = instance(s => if (s.length != 36) Left(new IllegalArgumentException(s"Too long for UUID: ${s.length}")) - else try { Right(UUID.fromString(s))} catch {case e: Throwable => Left(e)} + else + try Right(UUID.fromString(s)) + catch { case e: Throwable => Left(e) } ) } @@ -60,8 +72,8 @@ trait LowPriorityDecode { * 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] + 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))) } diff --git a/core/src/main/scala/io/finch/DecodePath.scala b/core/src/main/scala/io/finch/DecodePath.scala index 24a0aa425..ca3949272 100644 --- a/core/src/main/scala/io/finch/DecodePath.scala +++ b/core/src/main/scala/io/finch/DecodePath.scala @@ -1,12 +1,13 @@ package io.finch -import io.finch.internal.TooFastString import java.util.UUID +import io.finch.internal.TooFastString + /** - * Decodes an HTTP path (eg: /foo/bar/baz) represented as UTF-8 `String` into - * an arbitrary type `A`. - */ + * Decodes an HTTP path (eg: /foo/bar/baz) represented as UTF-8 `String` into + * an arbitrary type `A`. + */ trait DecodePath[A] { def apply(s: String): Option[A] } @@ -16,7 +17,7 @@ object DecodePath { @inline def apply[A](implicit d: DecodePath[A]): DecodePath[A] = d def instance[A](fn: String => Option[A]): DecodePath[A] = new DecodePath[A] { - def apply(s: String): Option[A] = fn( s) + def apply(s: String): Option[A] = fn(s) } implicit val decodePath: DecodePath[String] = instance(Some.apply) @@ -25,6 +26,8 @@ object DecodePath { implicit val decodeBoolean: DecodePath[Boolean] = instance(_.tooBoolean) implicit val decodeUUID: DecodePath[UUID] = instance { s => if (s.length != 36) None - else try Some(UUID.fromString(s)) catch { case _: Exception => None } + else + try Some(UUID.fromString(s)) + catch { case _: Exception => None } } } diff --git a/core/src/main/scala/io/finch/DecodeStream.scala b/core/src/main/scala/io/finch/DecodeStream.scala index b294ed629..cc8990c25 100644 --- a/core/src/main/scala/io/finch/DecodeStream.scala +++ b/core/src/main/scala/io/finch/DecodeStream.scala @@ -1,6 +1,7 @@ package io.finch import java.nio.charset.Charset + import scala.annotation.implicitNotFound import com.twitter.io.Buf @@ -20,7 +21,7 @@ trait DecodeStream[S[_[_], _], F[_], A] { object DecodeStream { @implicitNotFound( -"""A stream* endpoint requires implicit DecodeStream instance in scope, probably streaming decoder for ${A} is missing. + """A stream* endpoint requires implicit DecodeStream instance in scope, probably streaming decoder for ${A} is missing. Make sure ${A} is one of the following: @@ -30,18 +31,16 @@ object DecodeStream { Help: If you're looking for JSON stream decoding, consider to use decoder from finch-circe library """ ) - type Aux[S[_[_], _], F[_], A, CT <: String] = DecodeStream[S, F, A] {type ContentType = CT} + type Aux[S[_[_], _], F[_], A, CT <: String] = DecodeStream[S, F, A] { type ContentType = CT } type Json[S[_[_], _], F[_], A] = Aux[S, F, A, Application.Json] - def instance[S[_[_], _], F[_], A, CT <: String] - (f: (S[F, Buf], Charset) => S[F, A]): DecodeStream.Aux[S, F, A, CT] = { + def instance[S[_[_], _], F[_], A, CT <: String](f: (S[F, Buf], Charset) => S[F, A]): DecodeStream.Aux[S, F, A, CT] = new DecodeStream[S, F, A] { type ContentType = CT def apply(stream: S[F, Buf], cs: Charset): S[F, A] = f(stream, cs) } - } } diff --git a/core/src/main/scala/io/finch/Encode.scala b/core/src/main/scala/io/finch/Encode.scala index 636a58be8..e07eea797 100644 --- a/core/src/main/scala/io/finch/Encode.scala +++ b/core/src/main/scala/io/finch/Encode.scala @@ -1,12 +1,13 @@ package io.finch +import java.nio.charset.Charset + import cats.Show import com.twitter.io.Buf -import java.nio.charset.Charset /** - * Encodes an HTTP payload (represented as an arbitrary type `A`) with a given [[Charset]]. - */ + * Encodes an HTTP payload (represented as an arbitrary type `A`) with a given [[Charset]]. + */ trait Encode[A] { type ContentType <: String @@ -38,10 +39,10 @@ trait LowPriorityEncodeInstances { trait HighPriorityEncodeInstances extends LowPriorityEncodeInstances { - private[this] final val anyToEmptyBuf: Aux[Any, Nothing] = + final private[this] val anyToEmptyBuf: Aux[Any, Nothing] = instance[Any, Nothing]((_, _) => Buf.Empty) - private[this] final val bufToBuf: Aux[Buf, Nothing] = + final private[this] val bufToBuf: Aux[Buf, Nothing] = instance[Buf, Nothing]((buf, _) => buf) implicit def encodeUnit[CT <: String]: Aux[Unit, CT] = @@ -57,8 +58,8 @@ trait HighPriorityEncodeInstances extends LowPriorityEncodeInstances { object Encode extends HighPriorityEncodeInstances { /** - * Returns a [[Encode]] instance for a given type (with required content type). - */ + * Returns a [[Encode]] instance for a given type (with required content type). + */ @inline final def apply[A, CT <: String](implicit e: Aux[A, CT]): Aux[A, CT] = e implicit val encodeExceptionAsTextPlain: Text[Exception] = diff --git a/core/src/main/scala/io/finch/EncodeStream.scala b/core/src/main/scala/io/finch/EncodeStream.scala index 9d15cd8c9..2b8fb4412 100644 --- a/core/src/main/scala/io/finch/EncodeStream.scala +++ b/core/src/main/scala/io/finch/EncodeStream.scala @@ -1,11 +1,12 @@ package io.finch -import com.twitter.io.{Buf, Reader} import java.nio.charset.Charset +import com.twitter.io.{Buf, Reader} + /** - * A type-class that defines encoding of a stream in a shape of `S[F[_], A]` to Finagle's [[Reader]]. - */ + * A type-class that defines encoding of a stream in a shape of `S[F[_], A]` to Finagle's [[Reader]]. + */ trait EncodeStream[F[_], S[_[_], _], A] { type ContentType <: String @@ -18,9 +19,7 @@ object EncodeStream { type Aux[F[_], S[_[_], _], A, CT <: String] = EncodeStream[F, S, A] { type ContentType = CT } - type Json[F[_], S[_[_],_], A] = Aux[F, S, A, Application.Json] + type Json[F[_], S[_[_], _], A] = Aux[F, S, A, Application.Json] - type Text[F[_], S[_[_],_], A] = Aux[F, S, A, Text.Plain] + type Text[F[_], S[_[_], _], A] = Aux[F, S, A, Text.Plain] } - - diff --git a/core/src/main/scala/io/finch/Endpoint.scala b/core/src/main/scala/io/finch/Endpoint.scala index 8e1269dcf..002556a98 100644 --- a/core/src/main/scala/io/finch/Endpoint.scala +++ b/core/src/main/scala/io/finch/Endpoint.scala @@ -1,65 +1,68 @@ package io.finch -import cats.{~>, Alternative, Applicative, ApplicativeError, Id, Monad, MonadError} +import java.io.{File, FileInputStream, InputStream} + +import scala.reflect.ClassTag + import cats.data._ import cats.effect._ import cats.syntax.all._ +import cats.{Alternative, Applicative, ApplicativeError, Apply, Functor, Id, Monad, MonadError, MonoidK, SemigroupK, ~>} import com.twitter.finagle.Service -import com.twitter.finagle.http.{Cookie => FinagleCookie, Method => FinagleMethod, Request, Response} import com.twitter.finagle.http.exp.{Multipart => FinagleMultipart} +import com.twitter.finagle.http.{Cookie => FinagleCookie, Method => FinagleMethod, Request, Response} import com.twitter.io.Buf import io.finch.endpoint._ import io.finch.internal._ -import java.io.{File, FileInputStream, InputStream} -import scala.reflect.ClassTag import shapeless._ import shapeless.ops.adjoin.Adjoin import shapeless.ops.hlist.Tupler /** - * An `Endpoint` represents the HTTP endpoint. - * - * It is well known and widely adopted in Finagle that "Your Server is a Function" - * (i.e., `Request => Future[Response]`). In a REST/HTTP API setting this function may be viewed as - * `Request =1=> (A =2=> Future[B]) =3=> Future[Response]`, where transformation `1` is a request - * decoding (deserialization), transformation `2` - is a business logic and transformation `3` is - - * a response encoding (serialization). The only interesting part here is transformation `2` (i.e., - * `A => Future[B]`), which represents an application business. - * - * An `Endpoint` transformation (`map`, `mapAsync`, etc.) encodes the business logic, while the - * rest of Finch ecosystem takes care about both serialization and deserialization. - * - * A typical way to transform (or map) the `Endpoint` is to use [[internal.Mapper]]: - * - * {{{ - * import io.finch._ - * - * case class Foo(i: Int) - * case class Bar(s: String) - * - * val foo: Endpoint[Foo] = get("foo") { Ok(Foo(42)) } - * val bar: Endpoint[Bar] = get("bar" :: path[String]) { s: String => Ok(Bar(s)) } - * }}} - * - * `Endpoint`s are also composable in terms of or-else combinator (known as a "space invader" - * operator `:+:`) that takes two `Endpoint`s and returns a coproduct `Endpoint`. - * - * {{{ - * import io.finch._ - * - * val foobar: Endpoint[Foo :+: Bar :+: CNil] = foo :+: bar - * }}} - * - * An `Endpoint` might be converted into a Finagle [[Service]] with `Endpoint.toService` method so - * it can be served within Finagle HTTP. - * - * {{{ - * import com.twitter.finagle.Http - * - * Http.server.serve(foobar.toService) - * }}} - */ -trait Endpoint[F[_], A] { self => + * An `Endpoint` represents the HTTP endpoint. + * + * It is well known and widely adopted in Finagle that "Your Server is a Function" + * (i.e., `Request => Future[Response]`). In a REST/HTTP API setting this function may be viewed as + * `Request =1=> (A =2=> Future[B]) =3=> Future[Response]`, where transformation `1` is a request + * decoding (deserialization), transformation `2` - is a business logic and transformation `3` is - + * a response encoding (serialization). The only interesting part here is transformation `2` (i.e., + * `A => Future[B]`), which represents an application business. + * + * An `Endpoint` transformation (`map`, `mapAsync`, etc.) encodes the business logic, while the + * rest of Finch ecosystem takes care about both serialization and deserialization. + * + * A typical way to transform (or map) the `Endpoint` is to use [[internal.Mapper]]: + * + * {{{ + * import io.finch._ + * + * case class Foo(i: Int) + * case class Bar(s: String) + * + * val foo: Endpoint[Foo] = get("foo") { Ok(Foo(42)) } + * val bar: Endpoint[Bar] = get("bar" :: path[String]) { s: String => Ok(Bar(s)) } + * }}} + * + * `Endpoint`s are also composable in terms of or-else combinator (known as a "space invader" + * operator `:+:`) that takes two `Endpoint`s and returns a coproduct `Endpoint`. + * + * {{{ + * import io.finch._ + * + * val foobar: Endpoint[Foo :+: Bar :+: CNil] = foo :+: bar + * }}} + * + * An `Endpoint` might be converted into a Finagle [[Service]] with `Endpoint.toService` method so + * it can be served within Finagle HTTP. + * + * {{{ + * import com.twitter.finagle.Http + * + * Http.server.serve(foobar.toService) + * }}} + */ +trait Endpoint[F[_], A] { + self => /** * Request item (part) that's this endpoint work with. @@ -92,6 +95,7 @@ trait Endpoint[F[_], A] { self => } final override def item = self.item + final override def toString: String = self.toString } @@ -115,13 +119,13 @@ trait Endpoint[F[_], A] { self => } override def item = self.item + final override def toString: String = self.toString } /** * Transforms this endpoint to the given function `F[Output[A]] => F[Output[B]]`. * - * * Might be useful to perform some extra action on the underlying `Future`. For example, time * the latency of the given endpoint. * @@ -142,12 +146,13 @@ trait Endpoint[F[_], A] { self => } override def item = self.item + final override def toString: String = self.toString } /** - * Transform this endpoint to the given function `F[A] => F[B]` - */ + * Transform this endpoint to the given function `F[A] => F[B]` + */ @deprecated("Use .transform instead", "0.29") final def transformF[B](fn: F[A] => F[B])(implicit F: Monad[F]): Endpoint[F, B] = transform(fn) @@ -160,13 +165,18 @@ trait Endpoint[F[_], A] { self => def apply(input: Input): Endpoint.Result[F, B] = self(input) match { case EndpointResult.Matched(rem, trc, out) => - EndpointResult.Matched[F, B](rem, trc, out.flatMap { o => - o.traverse(a => fn(F.pure(a))) - }) + EndpointResult.Matched[F, B]( + rem, + trc, + out.flatMap { o => + o.traverse(a => fn(F.pure(a))) + } + ) case skipped: EndpointResult.NotMatched[F] => skipped } final override def item = self.item + final override def toString: String = self.toString } @@ -182,6 +192,7 @@ trait Endpoint[F[_], A] { self => } final override def item = self.item + final override def toString: String = self.toString } @@ -201,40 +212,42 @@ trait Endpoint[F[_], A] { self => * resulting type for product function. */ final def productWith[B, O](other: Endpoint[F, B])(p: (A, B) => O)(implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): Endpoint[F, O] = new Endpoint[F, O] with (((Either[Throwable, Output[A]], Either[Throwable, Output[B]])) => F[Output[O]]) { - private[this] final def collect(a: Throwable, b: Throwable): Throwable = (a, b) match { - case (aa: Error, bb: Error) => Errors(NonEmptyList.of(aa, bb)) - case (aa: Error, Errors(bs)) => Errors(aa :: bs) - case (Errors(as), bb: Error) => Errors(bb :: as) + final private[this] def collect(a: Throwable, b: Throwable): Throwable = (a, b) match { + case (aa: Error, bb: Error) => Errors(NonEmptyList.of(aa, bb)) + case (aa: Error, Errors(bs)) => Errors(aa :: bs) + case (Errors(as), bb: Error) => Errors(bb :: as) case (Errors(as), Errors(bs)) => Errors(as.concatNel(bs)) - case (_: Error, _) => b // we fail-fast with first non-Error observed - case (_: Errors, _) => b // we fail-fast with first non-Error observed - case _ => a + case (_: Error, _) => b // we fail-fast with first non-Error observed + case (_: Errors, _) => b // we fail-fast with first non-Error observed + case _ => a } final def apply(both: (Either[Throwable, Output[A]], Either[Throwable, Output[B]])): F[Output[O]] = both match { case (Right(oa), Right(ob)) => F.pure(oa.flatMap(a => ob.map(b => p(a, b)))) - case (Left(a), Left(b)) => F.raiseError(collect(a, b)) - case (Left(a), _) => F.raiseError(a) - case (_, Left(b)) => F.raiseError(b) + case (Left(a), Left(b)) => F.raiseError(collect(a, b)) + case (Left(a), _) => F.raiseError(a) + case (_, Left(b)) => F.raiseError(b) } final def apply(input: Input): Endpoint.Result[F, O] = self(input) match { - case a @ EndpointResult.Matched(_, _, _) => other(a.rem) match { - case b @ EndpointResult.Matched(_, _, _) => - EndpointResult.Matched( - b.rem, - a.trc.concat(b.trc), - a.out.attempt.product(b.out.attempt).flatMap(this) - ) - case skipped: EndpointResult.NotMatched[F] => skipped - } + case a @ EndpointResult.Matched(_, _, _) => + other(a.rem) match { + case b @ EndpointResult.Matched(_, _, _) => + EndpointResult.Matched( + b.rem, + a.trc.concat(b.trc), + a.out.attempt.product(b.out.attempt).flatMap(this) + ) + case skipped: EndpointResult.NotMatched[F] => skipped + } case skipped: EndpointResult.NotMatched[F] => skipped } override def item = self.item + final override def toString: String = self.toString } @@ -242,18 +255,19 @@ trait Endpoint[F[_], A] { self => * Composes this endpoint with the given [[Endpoint]]. */ final def ::[B](other: Endpoint[F, B])(implicit - pa: PairAdjoin[B, A], - F: MonadError[F, Throwable] + pa: PairAdjoin[B, A], + F: MonadError[F, Throwable] ): Endpoint[F, pa.Out] = new Endpoint[F, pa.Out] with ((B, A) => pa.Out) { - private[this] val inner = other.productWith(self)(this) + private[this] val inner = other.productWith(self)(this) - final def apply(b: B, a: A): pa.Out = pa(b, a) + final def apply(b: B, a: A): pa.Out = pa(b, a) - final def apply(input: Input): Endpoint.Result[F, pa.Out] = inner(input) + final def apply(input: Input): Endpoint.Result[F, pa.Out] = inner(input) - override def item = items.MultipleItems - final override def toString: String = s"${other.toString} :: ${self.toString}" - } + override def item = items.MultipleItems + + final override def toString: String = s"${other.toString} :: ${self.toString}" + } /** * Sequentially composes this endpoint with the given `other` endpoint. The resulting endpoint @@ -263,27 +277,31 @@ trait Endpoint[F[_], A] { self => * * - if both endpoints match, the result with a shorter remainder (in terms of consumed route) is picked * - if both endpoints don't match, the more specific result (explaining the reason for not matching) - * is picked + * is picked */ final def coproduct[B >: A](other: Endpoint[F, B]): Endpoint[F, B] = new Endpoint[F, B] { final def apply(input: Input): Endpoint.Result[F, B] = self(input) match { - case a @ EndpointResult.Matched(_, _, _) => other(input) match { - case b @ EndpointResult.Matched(_, _, _) => - if (a.rem.route.length <= b.rem.route.length) a else b - case _ => a - } - case a => other(input) match { - case EndpointResult.NotMatched.MethodNotAllowed(bb) => a match { - case EndpointResult.NotMatched.MethodNotAllowed(aa) => - EndpointResult.NotMatched.MethodNotAllowed(aa ++ bb) - case b => b + case a @ EndpointResult.Matched(_, _, _) => + other(input) match { + case b @ EndpointResult.Matched(_, _, _) => + if (a.rem.route.length <= b.rem.route.length) a else b + case _ => a + } + case a => + other(input) match { + case EndpointResult.NotMatched.MethodNotAllowed(bb) => + a match { + case EndpointResult.NotMatched.MethodNotAllowed(aa) => + EndpointResult.NotMatched.MethodNotAllowed(aa ++ bb) + case b => b + } + case _: EndpointResult.NotMatched[F] => a + case b => b } - case _: EndpointResult.NotMatched[F] => a - case b => b - } } override def item = items.MultipleItems + final override def toString: String = s"(${self.toString} :+: ${other.toString})" } @@ -291,8 +309,8 @@ trait Endpoint[F[_], A] { self => * Composes this endpoint with another in such a way that coproducts are flattened. */ final def :+:[B](that: Endpoint[F, B])(implicit - a: Adjoin[B :+: A :+: CNil], - F: MonadError[F, Throwable] + a: Adjoin[B :+: A :+: CNil], + F: MonadError[F, Throwable] ): Endpoint[F, a.Out] = { val left = that.map(x => a(Inl[B, A :+: CNil](x))) val right = self.map(x => a(Inr[B, A :+: CNil](Inl[A, CNil](x)))) @@ -306,9 +324,9 @@ trait Endpoint[F[_], A] { self => * Consider using [[io.finch.Bootstrap]] instead. */ final def toService(implicit - F: Effect[F], - tr: ToResponse.Aux[F, A, Application.Json], - tre: ToResponse.Aux[F, Exception, Application.Json] + F: Effect[F], + tr: ToResponse.Aux[F, A, Application.Json], + tre: ToResponse.Aux[F, Exception, Application.Json] ): Service[Request, Response] = toServiceAs[Application.Json] /** @@ -318,9 +336,9 @@ trait Endpoint[F[_], A] { self => * Consider using [[io.finch.Bootstrap]] instead. */ final def toServiceAs[CT <: String](implicit - F: Effect[F], - tr: ToResponse.Aux[F, A, CT], - tre: ToResponse.Aux[F, Exception, CT] + F: Effect[F], + tr: ToResponse.Aux[F, A, CT], + tre: ToResponse.Aux[F, Exception, CT] ): Service[Request, Response] = Bootstrap.serve[CT](this).toService /** @@ -330,9 +348,9 @@ trait Endpoint[F[_], A] { self => * Consider using [[io.finch.Bootstrap]] instead */ final def compile(implicit - F: MonadError[F, Throwable], - tr: ToResponse.Aux[F, A, Application.Json], - tre: ToResponse.Aux[F, Exception, Application.Json] + F: MonadError[F, Throwable], + tr: ToResponse.Aux[F, A, Application.Json], + tre: ToResponse.Aux[F, Exception, Application.Json] ): Endpoint.Compiled[F] = compileAs[Application.Json] /** @@ -342,9 +360,9 @@ trait Endpoint[F[_], A] { self => * Consider using [[io.finch.Bootstrap]] instead */ final def compileAs[CT <: String](implicit - F: MonadError[F, Throwable], - tr: ToResponse.Aux[F, A, CT], - tre: ToResponse.Aux[F, Exception, CT] + F: MonadError[F, Throwable], + tr: ToResponse.Aux[F, A, CT], + tre: ToResponse.Aux[F, Exception, CT] ): Endpoint.Compiled[F] = Bootstrap.serve[CT](this).compile /** @@ -352,7 +370,7 @@ trait Endpoint[F[_], A] { self => * handle any matching throwable from the underlying future. */ final def rescue(pf: PartialFunction[Throwable, F[Output[A]]])(implicit - F: ApplicativeError[F, Throwable] + F: ApplicativeError[F, Throwable] ): Endpoint[F, A] = transformOutput(foa => foa.recoverWith(pf)) /** @@ -360,16 +378,15 @@ trait Endpoint[F[_], A] { self => * handle any matching throwable from the underlying future. */ final def handle(pf: PartialFunction[Throwable, Output[A]])(implicit - F: ApplicativeError[F, Throwable] + F: ApplicativeError[F, Throwable] ): Endpoint[F, A] = rescue(pf.andThen(F.pure(_))) /** * Validates the result of this endpoint using a `predicate`. The rule is used for error * reporting. * - * @param rule text describing the rule being validated + * @param rule text describing the rule being validated * @param predicate returns true if the data is valid - * * @return an endpoint that will return the value of this reader if it is valid. * Otherwise the future fails with an [[Error.NotValid]] error. */ @@ -382,9 +399,8 @@ trait Endpoint[F[_], A] { self => /** * Validates the result of this endpoint using a `predicate`. The rule is used for error reporting. * - * @param rule text describing the rule being validated + * @param rule text describing the rule being validated * @param predicate returns false if the data is valid - * * @return an endpoint that will return the value of this reader if it is valid. * Otherwise the future fails with a [[Error.NotValid]] error. */ @@ -397,7 +413,6 @@ trait Endpoint[F[_], A] { self => * * @param rule the predefined [[ValidationRule]] that will return true if the data is * valid - * * @return an endpoint that will return the value of this reader if it is valid. * Otherwise the future fails with an [[Error.NotValid]] error. */ @@ -410,7 +425,6 @@ trait Endpoint[F[_], A] { self => * * @param rule the predefined [[ValidationRule]] that will return false if the data is * valid - * * @return an endpoint that will return the value of this reader if it is valid. * Otherwise the future fails with a [[Error.NotValid]] error. */ @@ -425,7 +439,7 @@ trait Endpoint[F[_], A] { self => new Endpoint[F, Either[Throwable, A]] with (Either[Throwable, Output[A]] => Output[Either[Throwable, A]]) { final def apply(toa: Either[Throwable, Output[A]]): Output[Either[Throwable, A]] = toa match { case Right(oo) => oo.map(Right.apply) - case Left(t) => Output.payload(Left(t)) + case Left(t) => Output.payload(Left(t)) } final def apply(input: Input): Endpoint.Result[F, Either[Throwable, A]] = self(input) match { @@ -435,7 +449,8 @@ trait Endpoint[F[_], A] { self => } override def item = self.item - override final def toString: String = self.toString + + final override def toString: String = self.toString } /** @@ -443,44 +458,46 @@ trait Endpoint[F[_], A] { self => */ final def withToString(ts: => String): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Endpoint.Result[F, A] = self(input) + final override def toString: String = ts } } /** - * Provides extension methods for [[Endpoint]] to support coproduct and path syntax. - */ + * Provides extension methods for [[Endpoint]] to support coproduct and path syntax. + */ object Endpoint { /** - * Enables a very simple syntax allowing to "map" endpoints to arbitrary functions. The types are - * resolved at compile time and no reflection is used. - * - * For example: - * - * {{{ - * import io.finch._ - * import io.cats.effect.IO - * - * object Mapping extends Endpoint.Module[IO] { - * def hello = get("hello" :: path[String]) { s: String => - * Ok(s) - * } - * } - * }}} - */ - trait Mappable[F[_], A] extends Endpoint[F, A] { self => + * Enables a very simple syntax allowing to "map" endpoints to arbitrary functions. The types are + * resolved at compile time and no reflection is used. + * + * For example: + * + * {{{ + * import io.finch._ + * import io.cats.effect.IO + * + * object Mapping extends Endpoint.Module[IO] { + * def hello = get("hello" :: path[String]) { s: String => + * Ok(s) + * } + * } + * }}} + */ + trait Mappable[F[_], A] extends Endpoint[F, A] { + self => final def apply(mapper: Mapper[F, A]): Endpoint[F, mapper.Out] = mapper(self) } /** - * An alias for [[EndpointResult]]. - */ + * An alias for [[EndpointResult]]. + */ type Result[F[_], A] = EndpointResult[F, A] /** - * An alias for [[EndpointModule]]. - */ + * An alias for [[EndpointModule]]. + */ type Module[F[_]] = EndpointModule[F] /** @@ -495,100 +512,120 @@ object Endpoint { } - final implicit class HListEndpointOps[F[_], L <: HList](val self: Endpoint[F, L]) extends AnyVal { + implicit final class HListEndpointOps[F[_], L <: HList](val self: Endpoint[F, L]) extends AnyVal { + /** - * Converts this endpoint to one that returns any type with this [[shapeless.HList]] as its - * representation. - */ + * Converts this endpoint to one that returns any type with this [[shapeless.HList]] as its + * representation. + */ def as[A](implicit gen: Generic.Aux[A, L], F: Monad[F]): Endpoint[F, A] = self.map(gen.from) /** - * Converts this endpoint to one that returns a tuple with the same types as this - * [[shapeless.HList]]. - * - * Note that this will fail at compile time if this this [[shapeless.HList]] contains more than - * 22 elements. - */ + * Converts this endpoint to one that returns a tuple with the same types as this + * [[shapeless.HList]]. + * + * Note that this will fail at compile time if this this [[shapeless.HList]] contains more than + * 22 elements. + */ def asTuple(implicit t: Tupler[L], F: Monad[F]): Endpoint[F, t.Out] = self.map(t(_)) } /** - * Implicit conversion that adds convenience methods to endpoint for optional values. - */ - final implicit class OptionEndpointOps[F[_], A](val self: Endpoint[F, Option[A]]) extends AnyVal { + * Implicit conversion that adds convenience methods to endpoint for optional values. + */ + implicit final class OptionEndpointOps[F[_], A](val self: Endpoint[F, Option[A]]) extends AnyVal { + /** - * If endpoint is empty it will return provided default value. - */ + * If endpoint is empty it will return provided default value. + */ def withDefault[B >: A](default: => B)(implicit F: Monad[F]): Endpoint[F, B] = self.map(_.getOrElse(default)) /** - * If endpoint is empty it will return provided alternative. - */ + * If endpoint is empty it will return provided alternative. + */ def orElse[B >: A](alternative: => Option[B])(implicit F: Monad[F]): Endpoint[F, Option[B]] = self.map(_.orElse(alternative)) } - final implicit class ValueEndpointOps[F[_], B](val self: Endpoint[F, B]) extends AnyVal { + implicit final class ValueEndpointOps[F[_], B](val self: Endpoint[F, B]) extends AnyVal { + /** - * Converts this endpoint to one that returns any type with `B :: HNil` as its representation. - */ + * Converts this endpoint to one that returns any type with `B :: HNil` as its representation. + */ def as[A](implicit gen: Generic.Aux[A, B :: HNil], F: Monad[F]): Endpoint[F, A] = self.map(value => gen.from(value :: HNil)) } - implicit def endpointInstances[F[_] : Sync]: Alternative[({type T[B] = Endpoint[F, B]})#T] = { - new Alternative[({type T[B] = Endpoint[F, B]})#T] { + private trait EndpointSemigroupK[F[_]] extends SemigroupK[Endpoint[F, *]] { + def combineK[A](x: Endpoint[F, A], y: Endpoint[F, A]): Endpoint[F, A] = + x.coproduct(y) + } + + private trait EndpointMonoidK[F[_]] extends EndpointSemigroupK[F] with MonoidK[Endpoint[F, *]] { + def empty[A]: Endpoint[F, A] = Endpoint.empty[F, A] + } + + private class EndpointFunctor[F[_]: Monad] extends Functor[Endpoint[F, *]] { + def map[A, B](fa: Endpoint[F, A])(f: A => B): Endpoint[F, B] = fa.map(f) + } - final override def ap[A, B](ff: Endpoint[F, A => B])(fa: Endpoint[F, A]): Endpoint[F, B] = - ff.productWith(fa)((f, a) => f(a)) + private class EndpointApply[F[_]](implicit F: MonadError[F, Throwable]) extends EndpointFunctor[F] with Apply[Endpoint[F, *]] { + def ap[A, B](ff: Endpoint[F, A => B])(fa: Endpoint[F, A]): Endpoint[F, B] = + ff.productWith(fa)((f, a) => f(a)) + } - final override def product[A, B](fa: Endpoint[F, A], fb: Endpoint[F, B]): Endpoint[F, (A, B)] = - fa.product(fb) + private class EndpointApplicative[F[_]](implicit F: MonadError[F, Throwable]) extends EndpointApply[F] with Applicative[Endpoint[F, *]] { + def pure[A](x: A): Endpoint[F, A] = + Endpoint.const[F, A](x) + } - final override def combineK[A](x: Endpoint[F, A], y: Endpoint[F, A]): Endpoint[F, A] = - x.coproduct(y) + private class EndpointAlternative[F[_]](implicit F: MonadError[F, Throwable]) + extends EndpointApplicative[F] + with EndpointMonoidK[F] + with Alternative[Endpoint[F, *]] - final override def pure[A](x: A): Endpoint[F, A] = - Endpoint.const[F, A](x) + implicit def endpointAlternative[F[_]](implicit F: MonadError[F, Throwable]): Alternative[Endpoint[F, *]] = + new EndpointAlternative[F] - final override def empty[A]: Endpoint[F, A] = - Endpoint.empty[F, A] + implicit def endpointApplicative[F[_]](implicit F: MonadError[F, Throwable]): Applicative[Endpoint[F, *]] = + new EndpointApplicative[F] - final override def map[A, B](fa: Endpoint[F, A])(f: A => B): Endpoint[F, B] = - fa.map(f) - } - } + implicit def endpointFunctor[F[_]: Monad]: Functor[Endpoint[F, *]] = + new EndpointFunctor[F] + + implicit def endpointMonoidK[F[_]]: MonoidK[Endpoint[F, *]] = + new EndpointMonoidK[F] {} /** - * Instantiates an [[EndpointModule]] for a given effect type `F`. This is enables better type - * inference when constucting endpoint instances. - * - * For example, `lift` infer the resulting endpoint based on the argument type (string): - * - * {{{ - * import io.finch._, cats.effect.IO - * val e = Endpoint[IO].lift("foo") // Endpoint[IO, String] - * }}} - */ + * Instantiates an [[EndpointModule]] for a given effect type `F`. This is enables better type + * inference when constucting endpoint instances. + * + * For example, `lift` infer the resulting endpoint based on the argument type (string): + * + * {{{ + * import io.finch._, cats.effect.IO + * val e = Endpoint[IO].lift("foo") // Endpoint[IO, String] + * }}} + */ def apply[F[_]]: EndpointModule[F] = EndpointModule[F] /** * Convert [[Endpoint.Compiled]] into Finagle Service */ - def toService[F[_] : Effect](compiled: Endpoint.Compiled[F]): Service[Request, Response] = ToService(compiled) + def toService[F[_]: Effect](compiled: Endpoint.Compiled[F]): Service[Request, Response] = ToService(compiled) /** - * Creates an empty [[Endpoint]] (an endpoint that never matches) for a given type. - */ + * Creates an empty [[Endpoint]] (an endpoint that never matches) for a given type. + */ def empty[F[_], A]: Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = EndpointResult.NotMatched[F] } /** - * An [[Endpoint]] that, when composed with other endpoints, doesn't change anything. - */ + * An [[Endpoint]] that, when composed with other endpoints, doesn't change anything. + */ def zero[F[_]](implicit F: Applicative[F]): Endpoint[F, HNil] = new Endpoint[F, HNil] { final def apply(input: Input): Result[F, HNil] = @@ -596,8 +633,8 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that always matches and returns a given value (evaluated eagerly). - */ + * Creates an [[Endpoint]] that always matches and returns a given value (evaluated eagerly). + */ def const[F[_], A](a: A)(implicit F: Applicative[F]): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = @@ -605,17 +642,17 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that always matches and returns a given value (evaluated lazily). - * - * This might be useful for wrapping functions returning arbitrary value within [[Endpoint]] - * context. - * - * Example: the following endpoint will recompute a random integer on each request. - * - * {{{ - * val nextInt: Endpoint[Int] = Endpoint.lift(scala.util.random.nextInt) - * }}} - */ + * Creates an [[Endpoint]] that always matches and returns a given value (evaluated lazily). + * + * This might be useful for wrapping functions returning arbitrary value within [[Endpoint]] + * context. + * + * Example: the following endpoint will recompute a random integer on each request. + * + * {{{ + * val nextInt: Endpoint[Int] = Endpoint.lift(scala.util.random.nextInt) + * }}} + */ def lift[F[_], A](a: => A)(implicit F: Sync[F]): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = @@ -623,8 +660,8 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that always matches and returns a given `F` (evaluated lazily). - */ + * Creates an [[Endpoint]] that always matches and returns a given `F` (evaluated lazily). + */ def liftAsync[F[_], A](fa: => F[A])(implicit F: Sync[F]): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = @@ -632,8 +669,8 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that always matches and returns a given `Output` (evaluated lazily). - */ + * Creates an [[Endpoint]] that always matches and returns a given `Output` (evaluated lazily). + */ def liftOutput[F[_], A](oa: => Output[A])(implicit F: Sync[F]): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = @@ -641,9 +678,9 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that always matches and returns a given `F[Output]` - * (evaluated lazily). - */ + * Creates an [[Endpoint]] that always matches and returns a given `F[Output]` + * (evaluated lazily). + */ def liftOutputAsync[F[_], A](foa: => F[Output[A]])(implicit F: Sync[F]): Endpoint[F, A] = new Endpoint[F, A] { final def apply(input: Input): Result[F, A] = @@ -651,62 +688,64 @@ object Endpoint { } /** - * Creates an [[Endpoint]] from a given [[InputStream]]. Uses [[Resource]] for safer resource - * management and [[ContextShift]] for offloading blocking work from a worker pool. - * - * @see [[fromFile]] - */ - def fromInputStream[F[_]](stream: Resource[F, InputStream])( - implicit F: Sync[F], S: ContextShift[F] + * Creates an [[Endpoint]] from a given [[InputStream]]. Uses [[Resource]] for safer resource + * management and [[ContextShift]] for offloading blocking work from a worker pool. + * + * @see [[fromFile]] + */ + def fromInputStream[F[_]](stream: Resource[F, InputStream])(implicit + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = new FromInputStream[F](stream) /** - * Creates an [[Endpoint]] from a given [[File]]. Uses [[Resource]] for safer resource - * management and [[ContextShift]] for offloading blocking work from a worker pool. - * - * @see [[fromInputStream]] - */ - def fromFile[F[_]](file: File)( - implicit F: Sync[F], S: ContextShift[F] + * Creates an [[Endpoint]] from a given [[File]]. Uses [[Resource]] for safer resource + * management and [[ContextShift]] for offloading blocking work from a worker pool. + * + * @see [[fromInputStream]] + */ + def fromFile[F[_]](file: File)(implicit + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = fromInputStream[F]( Resource.fromAutoCloseable(F.delay(new FileInputStream(file))) ) /** - * Creates an [[Endpoint]] that serves an asset (static content) from a Java classpath resource, - * located at `path`, as a static content. The returned endpoint will only match `GET` requests - * with path identical to asset's. - * - * This could be especially useful in local development, when throughput and latency matter less - * than quick iterations. These means, however, are not recommended for production usage. Web - * servers (Nginx, Apache) will do much better job serving static files. - * - * Example project structure: - * - * {{{ - * ├── scala - * │ └── Main.scala - * └── resources - * ├── index.html - * └── script.js - * }}} - * - * Example bootstrap: - * - * {{{ - * Bootstrap - * ... - * .serve[Text.Html](Endpoint[IO].classpathAsset("/index.html")) - * .serve[Application.Javascript](Endpoint[IO].classpathAsset("/script.js")) - * ... - * }}} - * - * @see https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html - */ + * Creates an [[Endpoint]] that serves an asset (static content) from a Java classpath resource, + * located at `path`, as a static content. The returned endpoint will only match `GET` requests + * with path identical to asset's. + * + * This could be especially useful in local development, when throughput and latency matter less + * than quick iterations. These means, however, are not recommended for production usage. Web + * servers (Nginx, Apache) will do much better job serving static files. + * + * Example project structure: + * + * {{{ + * ├── scala + * │ └── Main.scala + * └── resources + * ├── index.html + * └── script.js + * }}} + * + * Example bootstrap: + * + * {{{ + * Bootstrap + * ... + * .serve[Text.Html](Endpoint[IO].classpathAsset("/index.html")) + * .serve[Application.Javascript](Endpoint[IO].classpathAsset("/script.js")) + * ... + * }}} + * + * @see https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html + */ def classpathAsset[F[_]](path: String)(implicit - F: Sync[F], - S: ContextShift[F] + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = { val asset = new Asset[F](path) val stream = @@ -716,23 +755,23 @@ object Endpoint { } /** - * Creates an [[Endpoint]] that serves an asset (static content) from a filesystem, located at - * `path`, as a static content. The returned endpoint will only match `GET` requests with path - * identical to asset's. - * - * Example bootstrap: - * - * {{{ - * Bootstrap - * ... - * .serve[Text.Html](Endpoint[IO].filesystemAsset("index.html")) - * .serve[Application.Javascript](Endpoint[IO].filesystemAsset("script.js")) - * ... - * }}} - */ + * Creates an [[Endpoint]] that serves an asset (static content) from a filesystem, located at + * `path`, as a static content. The returned endpoint will only match `GET` requests with path + * identical to asset's. + * + * Example bootstrap: + * + * {{{ + * Bootstrap + * ... + * .serve[Text.Html](Endpoint[IO].filesystemAsset("index.html")) + * .serve[Application.Javascript](Endpoint[IO].filesystemAsset("script.js")) + * ... + * }}} + */ def filesystemAsset[F[_]](path: String)(implicit - F: Sync[F], - S: ContextShift[F] + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = { val asset = new Asset[F](path) val file = fromFile[F](new File(path)) @@ -741,8 +780,8 @@ object Endpoint { } /** - * A root [[Endpoint]] that always matches and extracts the current request. - */ + * A root [[Endpoint]] that always matches and extracts the current request. + */ def root[F[_]](implicit F: Sync[F]): Endpoint[F, Request] = new Endpoint[F, Request] { final def apply(input: Input): Result[F, Request] = @@ -752,8 +791,8 @@ object Endpoint { } /** - * An [[Endpoint]] that always matches any path. - */ + * An [[Endpoint]] that always matches any path. + */ def pathAny[F[_]](implicit F: Applicative[F]): Endpoint[F, HNil] = new Endpoint[F, HNil] { final def apply(input: Input): Result[F, HNil] = @@ -767,9 +806,8 @@ object Endpoint { } /** - * - * An [[Endpoint]] that matches an empty path. - */ + * An [[Endpoint]] that matches an empty path. + */ def pathEmpty[F[_]](implicit F: Applicative[F]): Endpoint[F, HNil] = new Endpoint[F, HNil] { final def apply(input: Input): Result[F, HNil] = @@ -781,353 +819,353 @@ object Endpoint { } /** - * A matching [[Endpoint]] that reads a value of type `A` (using the implicit - * [[DecodePath]] instances defined for `A`) from the current path segment. - */ + * A matching [[Endpoint]] that reads a value of type `A` (using the implicit + * [[DecodePath]] instances defined for `A`) from the current path segment. + */ def path[F[_]: Sync, A: DecodePath: ClassTag]: Endpoint[F, A] = new ExtractPath[F, A] /** - * A matching [[Endpoint]] that reads a tail value `A` (using the implicit - * [[DecodePath]] instances defined for `A`) from the entire path. - */ + * A matching [[Endpoint]] that reads a tail value `A` (using the implicit + * [[DecodePath]] instances defined for `A`) from the entire path. + */ def paths[F[_]: Sync, A: DecodePath: ClassTag]: Endpoint[F, List[A]] = new ExtractPaths[F, A] /** - * An [[Endpoint]] that matches a given string. - */ + * An [[Endpoint]] that matches a given string. + */ def path[F[_]: Sync](s: String): Endpoint[F, HNil] = new MatchPath[F](s) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `GET` and the underlying - * endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `GET` and the underlying + * endpoint succeeds on it. + */ def get[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Get, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `POST` and the underlying - * endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `POST` and the underlying + * endpoint succeeds on it. + */ def post[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Post, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `PATCH` and the underlying - * endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `PATCH` and the underlying + * endpoint succeeds on it. + */ def patch[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Patch, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `DELETE` and the - * underlying endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `DELETE` and the + * underlying endpoint succeeds on it. + */ def delete[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Delete, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `HEAD` and the underlying - * endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `HEAD` and the underlying + * endpoint succeeds on it. + */ def head[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Head, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `OPTIONS` and the - * underlying endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `OPTIONS` and the + * underlying endpoint succeeds on it. + */ def options[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Options, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `PUT` and the underlying - * endpoint succeeds on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `PUT` and the underlying + * endpoint succeeds on it. + */ def put[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Put, e) /** - * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The - * resulting [[Endpoint]] succeeds on the request only if its method is `TRACE` and the underlying - * router endpoint on it. - */ + * A combinator that wraps the given [[Endpoint]] with additional check of the HTTP method. The + * resulting [[Endpoint]] succeeds on the request only if its method is `TRACE` and the underlying + * router endpoint on it. + */ def trace[F[_], A](e: Endpoint[F, A]): Mappable[F, A] = new Method[F, A](FinagleMethod.Trace, e) /** - * An evaluating [[Endpoint]] that reads a required HTTP header `name` from the request or raises - * an [[Error.NotPresent]] exception when the header is missing. - */ + * An evaluating [[Endpoint]] that reads a required HTTP header `name` from the request or raises + * an [[Error.NotPresent]] exception when the header is missing. + */ def header[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = new Header[F, Id, A](name) with Header.Required[F, A] /** - * An evaluating [[Endpoint]] that reads an optional HTTP header `name` from the request into an - * `Option`. - */ + * An evaluating [[Endpoint]] that reads an optional HTTP header `name` from the request into an + * `Option`. + */ def headerOption[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = new Header[F, Option, A](name) with Header.Optional[F, A] /** - * An evaluating [[Endpoint]] that reads a binary request body, interpreted as a `Array[Byte]`, - * into an `Option`. The returned [[Endpoint]] only matches non-chunked (non-streamed) requests. - */ + * An evaluating [[Endpoint]] that reads a binary request body, interpreted as a `Array[Byte]`, + * into an `Option`. The returned [[Endpoint]] only matches non-chunked (non-streamed) requests. + */ def binaryBodyOption[F[_]: Sync]: Endpoint[F, Option[Array[Byte]]] = new BinaryBody[F, Option[Array[Byte]]] with FullBody.Optional[F, Array[Byte]] /** - * An evaluating [[Endpoint]] that reads a required binary request body, interpreted as an - * `Array[Byte]`, or throws a [[Error.NotPresent]] exception. The returned [[Endpoint]] only - * matches non-chunked (non-streamed) requests. - */ + * An evaluating [[Endpoint]] that reads a required binary request body, interpreted as an + * `Array[Byte]`, or throws a [[Error.NotPresent]] exception. The returned [[Endpoint]] only + * matches non-chunked (non-streamed) requests. + */ def binaryBody[F[_]: Sync]: Endpoint[F, Array[Byte]] = new BinaryBody[F, Array[Byte]] with FullBody.Required[F, Array[Byte]] /** - * An evaluating [[Endpoint]] that reads an optional request body, interpreted as a `String`, into - * an `Option`. The returned [[Endpoint]] only matches non-chunked (non-streamed) requests. - */ + * An evaluating [[Endpoint]] that reads an optional request body, interpreted as a `String`, into + * an `Option`. The returned [[Endpoint]] only matches non-chunked (non-streamed) requests. + */ def stringBodyOption[F[_]: Sync]: Endpoint[F, Option[String]] = new StringBody[F, Option[String]] with FullBody.Optional[F, String] /** - * An evaluating [[Endpoint]] that reads the required request body, interpreted as a `String`, or - * throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] only matches non-chunked - * (non-streamed) requests. - */ + * An evaluating [[Endpoint]] that reads the required request body, interpreted as a `String`, or + * throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] only matches non-chunked + * (non-streamed) requests. + */ def stringBody[F[_]: Sync]: Endpoint[F, String] = new StringBody[F, String] with FullBody.Required[F, String] /** - * An [[Endpoint]] that reads an optional request body represented as `CT` (`ContentType`) and - * interpreted as `A`, into an `Option`. The returned [[Endpoint]] only matches non-chunked - * (non-streamed) requests. - */ + * An [[Endpoint]] that reads an optional request body represented as `CT` (`ContentType`) and + * interpreted as `A`, into an `Option`. The returned [[Endpoint]] only matches non-chunked + * (non-streamed) requests. + */ def bodyOption[F[_]: Sync, A: ClassTag, CT](implicit D: Decode.Dispatchable[A, CT]): Endpoint[F, Option[A]] = new Body[F, A, Option[A], CT] with FullBody.Optional[F, A] /** - * An [[Endpoint]] that reads the required request body represented as `CT` (`ContentType`) and - * interpreted as `A`, or throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] - * only matches non-chunked (non-streamed) requests. - */ + * An [[Endpoint]] that reads the required request body represented as `CT` (`ContentType`) and + * interpreted as `A`, or throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] + * only matches non-chunked (non-streamed) requests. + */ def body[F[_]: Sync, A: ClassTag, CT](implicit d: Decode.Dispatchable[A, CT]): Endpoint[F, A] = new Body[F, A, A, CT] with FullBody.Required[F, A] /** - * Alias for `body[F, A, Application.Json]`. - */ + * Alias for `body[F, A, Application.Json]`. + */ def jsonBody[F[_]: Sync, A: Decode.Json: ClassTag]: Endpoint[F, A] = body[F, A, Application.Json] /** - * Alias for `bodyOption[F, A, Application.Json]`. - */ + * Alias for `bodyOption[F, A, Application.Json]`. + */ def jsonBodyOption[F[_]: Sync, A: Decode.Json: ClassTag]: Endpoint[F, Option[A]] = bodyOption[F, A, Application.Json] /** - * Alias for `body[F, A, Text.Plain]` - */ + * Alias for `body[F, A, Text.Plain]` + */ def textBody[F[_]: Sync, A: Decode.Text: ClassTag]: Endpoint[F, A] = body[F, A, Text.Plain] /** - * Alias for `bodyOption[A, Text.Plain]` - */ + * Alias for `bodyOption[A, Text.Plain]` + */ def textBodyOption[F[_]: Sync, A: Decode.Text: ClassTag]: Endpoint[F, Option[A]] = bodyOption[F, A, Text.Plain] /** - * An [[Endpoint]] that matches chunked requests and lifts their content into a generic - * **binary** stream passed as a type parameter. This method, along with other `bodyStream` - * endpoints, are integration points with streaming libraries such as fs2 and iteratee. - * - * {{{ - * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator - * - * scala> val bin = Endpoint[IO].binaryBodyStream[Enumerator] - * bin: Endpoint[IO, Enumerator[IO, Array[Byte]]] = binaryBodyStream - * }}} - */ + * An [[Endpoint]] that matches chunked requests and lifts their content into a generic + * **binary** stream passed as a type parameter. This method, along with other `bodyStream` + * endpoints, are integration points with streaming libraries such as fs2 and iteratee. + * + * {{{ + * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator + * + * scala> val bin = Endpoint[IO].binaryBodyStream[Enumerator] + * bin: Endpoint[IO, Enumerator[IO, Array[Byte]]] = binaryBodyStream + * }}} + */ def binaryBodyStream[F[_]: Sync, S[_[_], _]](implicit - LR: LiftReader[S, F] + LR: LiftReader[S, F] ): Endpoint[F, S[F, Array[Byte]]] = new BinaryBodyStream[F, S] /** - * An [[Endpoint]] that matches chunked requests and lifts their content into a generic - * **string** stream passed as a type parameter. This method, along with other `bodyStream` - * endpoints, are integration points with streaming libraries such as fs2 and iteratee. - * - * {{{ - * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator - * - * scala> val bin = Endpoint[IO].stringBodyStream[Enumerator] - * bin: Endpoint[IO, Enumerator[IO, String]] = stringBodyStream - * }}} - */ + * An [[Endpoint]] that matches chunked requests and lifts their content into a generic + * **string** stream passed as a type parameter. This method, along with other `bodyStream` + * endpoints, are integration points with streaming libraries such as fs2 and iteratee. + * + * {{{ + * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator + * + * scala> val bin = Endpoint[IO].stringBodyStream[Enumerator] + * bin: Endpoint[IO, Enumerator[IO, String]] = stringBodyStream + * }}} + */ def stringBodyStream[F[_]: Sync, S[_[_], _]](implicit - LR: LiftReader[S, F] + LR: LiftReader[S, F] ): Endpoint[F, S[F, String]] = new StringBodyStream[F, S] /** - * An [[Endpoint]] that matches chunked requests and lifts their content into a generic - * stream passed as a type parameter. This method, along with other `bodyStream` - * endpoints, are integration points with streaming libraries such as fs2 and iteratee. - * - * When, for example, JSON library is import, this endpoint can parse an inbound JSON stream. - * - * {{{ - * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator - * - * scala> import io.finch.circe._, io.circe.generic.auto._ - * - * scala> case class Foo(s: String) - - * scala> val json = Endpoint[IO].bodyStream[Enumerator, Foo, Application.Json] - * bin: Endpoint[IO, Enumerator[IO, Foo]] = bodyStream - * }}} - */ + * An [[Endpoint]] that matches chunked requests and lifts their content into a generic + * stream passed as a type parameter. This method, along with other `bodyStream` + * endpoints, are integration points with streaming libraries such as fs2 and iteratee. + * + * When, for example, JSON library is import, this endpoint can parse an inbound JSON stream. + * + * {{{ + * scala> import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator + * + * scala> import io.finch.circe._, io.circe.generic.auto._ + * + * scala> case class Foo(s: String) + * + * scala> val json = Endpoint[IO].bodyStream[Enumerator, Foo, Application.Json] + * bin: Endpoint[IO, Enumerator[IO, Foo]] = bodyStream + * }}} + */ def bodyStream[F[_]: Sync, S[_[_], _], A, CT <: String](implicit - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, CT] + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, CT] ): Endpoint[F, S[F, A]] = new BodyStream[F, S, A, CT] /** - * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Application.Json]`. - */ + * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Application.Json]`. + */ def jsonBodyStream[F[_]: Sync, S[_[_], _], A](implicit - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, Application.Json] - ) : Endpoint[F, S[F, A]] = bodyStream[F, S, A, Application.Json] + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, Application.Json] + ): Endpoint[F, S[F, A]] = bodyStream[F, S, A, Application.Json] /** - * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Text.Plain]`. - */ + * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Text.Plain]`. + */ def textBodyStream[F[_]: Sync, S[_[_], _], A](implicit - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, Text.Plain] + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, Text.Plain] ): Endpoint[F, S[F, A]] = bodyStream[F, S, A, Text.Plain] /** - * An evaluating [[Endpoint]] that reads an optional HTTP cookie from the request into an - * `Option`. - */ + * An evaluating [[Endpoint]] that reads an optional HTTP cookie from the request into an + * `Option`. + */ def cookieOption[F[_]: Sync](name: String): Endpoint[F, Option[FinagleCookie]] = new Cookie[F, Option[FinagleCookie]](name) with Cookie.Optional[F] /** - * An evaluating [[Endpoint]] that reads a required cookie from the request or raises an - * [[Error.NotPresent]] exception when the cookie is missing. - */ + * An evaluating [[Endpoint]] that reads a required cookie from the request or raises an + * [[Error.NotPresent]] exception when the cookie is missing. + */ def cookie[F[_]: Sync](name: String): Endpoint[F, FinagleCookie] = new Cookie[F, FinagleCookie](name) with Cookie.Required[F] /** - * An evaluating [[Endpoint]] that reads an optional query-string param `name` from the request - * into an `Option`. - */ + * An evaluating [[Endpoint]] that reads an optional query-string param `name` from the request + * into an `Option`. + */ def paramOption[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = new Param[F, Option, A](name) with Param.Optional[F, A] /** - * An evaluating [[Endpoint]] that reads a required query-string param `name` from the - * request or raises an [[Error.NotPresent]] exception when the param is missing; an - * [[Error.NotValid]] exception is the param is empty. - */ + * An evaluating [[Endpoint]] that reads a required query-string param `name` from the + * request or raises an [[Error.NotPresent]] exception when the param is missing; an + * [[Error.NotValid]] exception is the param is empty. + */ def param[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = new Param[F, Id, A](name) with Param.Required[F, A] /** - * An evaluating [[Endpoint]] that reads an optional (in a meaning that a resulting - * `Seq` may be empty) multi-value query-string param `name` from the request into a `Seq`. - */ + * An evaluating [[Endpoint]] that reads an optional (in a meaning that a resulting + * `Seq` may be empty) multi-value query-string param `name` from the request into a `Seq`. + */ def params[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, List[A]] = new Params[F, List, A](name) with Params.AllowEmpty[F, A] /** - * An evaluating [[Endpoint]] that reads a required multi-value query-string param `name` - * from the request into a `NonEmptyList` or raises a [[Error.NotPresent]] exception - * when the params are missing or empty. - */ + * An evaluating [[Endpoint]] that reads a required multi-value query-string param `name` + * from the request into a `NonEmptyList` or raises a [[Error.NotPresent]] exception + * when the params are missing or empty. + */ def paramsNel[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, NonEmptyList[A]] = new Params[F, NonEmptyList, A](name) with Params.NonEmpty[F, A] /** - * An evaluating [[Endpoint]] that reads an optional file upload from a `multipart/form-data` - * request into an `Option`. - */ + * An evaluating [[Endpoint]] that reads an optional file upload from a `multipart/form-data` + * request into an `Option`. + */ def multipartFileUploadOption[F[_]: Sync](name: String): Endpoint[F, Option[FinagleMultipart.FileUpload]] = new FileUpload[F, Option](name) with FileUpload.Optional[F] /** - * An evaluating [[Endpoint]] that reads a required file upload from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that reads a required file upload from a `multipart/form-data` + * request. + */ def multipartFileUpload[F[_]: Sync](name: String): Endpoint[F, FinagleMultipart.FileUpload] = new FileUpload[F, Id](name) with FileUpload.Required[F] /** - * An evaluating [[Endpoint]] that optionally reads multiple file uploads from a - * `multipart/form-data` request. - */ + * An evaluating [[Endpoint]] that optionally reads multiple file uploads from a + * `multipart/form-data` request. + */ def multipartFileUploads[F[_]: Sync](name: String): Endpoint[F, List[FinagleMultipart.FileUpload]] = new FileUpload[F, List](name) with FileUpload.AllowEmpty[F] /** - * An evaluating [[Endpoint]] that requires multiple file uploads from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that requires multiple file uploads from a `multipart/form-data` + * request. + */ def multipartFileUploadsNel[F[_]: Sync](name: String): Endpoint[F, NonEmptyList[FinagleMultipart.FileUpload]] = new FileUpload[F, NonEmptyList](name) with FileUpload.NonEmpty[F] /** - * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` + * request. + */ def multipartAttribute[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = new Attribute[F, Id, A](name) with Attribute.Required[F, A] with Attribute.SingleError[F, Id, A] /** - * An evaluating [[Endpoint]] that reads an optional attribute from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that reads an optional attribute from a `multipart/form-data` + * request. + */ def multipartAttributeOption[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = new Attribute[F, Option, A](name) with Attribute.Optional[F, A] with Attribute.SingleError[F, Option, A] /** - * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` + * request. + */ def multipartAttributes[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, List[A]] = new Attribute[F, List, A](name) with Attribute.AllowEmpty[F, A] with Attribute.MultipleErrors[F, List, A] /** - * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` - * request. - */ + * An evaluating [[Endpoint]] that reads a required attribute from a `multipart/form-data` + * request. + */ def multipartAttributesNel[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, NonEmptyList[A]] = new Attribute[F, NonEmptyList, A](name) with Attribute.NonEmpty[F, A] with Attribute.MultipleErrors[F, NonEmptyList, A] /** - * Sequentially composes the given `endpoints` by using [[Endpoint!.coproduct]]. - * - * The resulting endpoint will match if at least one of the provided endpoints matches. - * If the sequence of provided endpoints is empty, the empty endpoint is returned, which never matches. - * - * @see [[Endpoint!.coproduct]] for the exact composition semantics. - * @see [[Endpoint.empty]] for the semantics of the empty endpoint. - */ + * Sequentially composes the given `endpoints` by using [[Endpoint!.coproduct]]. + * + * The resulting endpoint will match if at least one of the provided endpoints matches. + * If the sequence of provided endpoints is empty, the empty endpoint is returned, which never matches. + * + * @see [[Endpoint!.coproduct]] for the exact composition semantics. + * @see [[Endpoint.empty]] for the semantics of the empty endpoint. + */ def coproductAll[F[_], A](endpoints: Endpoint[F, A]*): Endpoint[F, A] = if (endpoints.isEmpty) empty else endpoints.reduce(_ coproduct _) } diff --git a/core/src/main/scala/io/finch/EndpointModule.scala b/core/src/main/scala/io/finch/EndpointModule.scala index 68889b0de..1aff561db 100644 --- a/core/src/main/scala/io/finch/EndpointModule.scala +++ b/core/src/main/scala/io/finch/EndpointModule.scala @@ -1,411 +1,415 @@ package io.finch +import java.io.{File, InputStream} + +import scala.reflect.ClassTag + import cats.Applicative import cats.data.NonEmptyList import cats.effect.{ContextShift, Resource, Sync} -import com.twitter.finagle.http.{Cookie, Request} import com.twitter.finagle.http.exp.Multipart +import com.twitter.finagle.http.{Cookie, Request} import com.twitter.io.Buf -import java.io.{File, InputStream} -import scala.reflect.ClassTag import shapeless.HNil /** - * Enables users to construct [[Endpoint]] instances without specifying the effect type `F[_]` every - * time. - * - * For example, via extending the `Endpoint.Module[F[_]]`: - * - * {{{ - * import io.finch._ - * import io.cats.effect.IO - * - * object Main extends App with Endpoint.Module[IO] { - * def foo = path("foo") - * } - * }}} - * - * It's also possible to instantiate an [[EndpointModule]] for a given effect and import its symbols - * into the score. For example: - * - * {{{ - * import io.finch._ - * import io.cats.effect.IO - * - * object Main extends App { - * val io = Endpoint[IO] - * import io._ - * - * def foo = path("foo") - * } - * }}} - * - * There is a pre-defined [[EndpointModule]] for Cats' `IO`, available via the import: - * - * {{{ - * import io.finch._ - * import io.finch.catsEffect._ - * - * object Main extends App { - * def foo = path("foo") - * } - * }}} - */ + * Enables users to construct [[Endpoint]] instances without specifying the effect type `F[_]` every + * time. + * + * For example, via extending the `Endpoint.Module[F[_]]`: + * + * {{{ + * import io.finch._ + * import io.cats.effect.IO + * + * object Main extends App with Endpoint.Module[IO] { + * def foo = path("foo") + * } + * }}} + * + * It's also possible to instantiate an [[EndpointModule]] for a given effect and import its symbols + * into the score. For example: + * + * {{{ + * import io.finch._ + * import io.cats.effect.IO + * + * object Main extends App { + * val io = Endpoint[IO] + * import io._ + * + * def foo = path("foo") + * } + * }}} + * + * There is a pre-defined [[EndpointModule]] for Cats' `IO`, available via the import: + * + * {{{ + * import io.finch._ + * import io.finch.catsEffect._ + * + * object Main extends App { + * def foo = path("foo") + * } + * }}} + */ trait EndpointModule[F[_]] { /** - * An alias for [[Endpoint.empty]]. - */ + * An alias for [[Endpoint.empty]]. + */ def empty[A]: Endpoint[F, A] = Endpoint.empty[F, A] /** - * An alias for [[Endpoint.zero]]. - */ + * An alias for [[Endpoint.zero]]. + */ def zero(implicit F: Applicative[F]): Endpoint[F, HNil] = Endpoint.zero[F] /** - * An alias for [[Endpoint.const]]. - */ + * An alias for [[Endpoint.const]]. + */ def const[A](a: A)(implicit F: Applicative[F]): Endpoint[F, A] = Endpoint.const[F, A](a) /** - * An alias for [[Endpoint.lift()]]. - */ + * An alias for [[Endpoint.lift()]]. + */ def lift[A](a: => A)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.lift[F, A](a) /** - * An alias for [[Endpoint.liftAsync]]. - */ + * An alias for [[Endpoint.liftAsync]]. + */ def liftAsync[A](fa: => F[A])(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.liftAsync[F, A](fa) /** - * An alias for [[Endpoint.liftOutput]]. - */ + * An alias for [[Endpoint.liftOutput]]. + */ def liftOutput[A](oa: => Output[A])(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.liftOutput[F, A](oa) /** - * An alias for [[Endpoint.liftOutputAsync]]. - */ + * An alias for [[Endpoint.liftOutputAsync]]. + */ def liftOutputAsync[A](foa: => F[Output[A]])(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.liftOutputAsync[F, A](foa) /** - * An alias for [[Endpoint.fromInputStream]]. - */ - def fromInputStream(stream: Resource[F, InputStream])( - implicit F: Sync[F], S: ContextShift[F] + * An alias for [[Endpoint.fromInputStream]]. + */ + def fromInputStream(stream: Resource[F, InputStream])(implicit + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = Endpoint.fromInputStream[F](stream) /** - * An alias for [[Endpoint.fromFile]]. - */ - def fromFile(file: File)( - implicit F: Sync[F], S: ContextShift[F] + * An alias for [[Endpoint.fromFile]]. + */ + def fromFile(file: File)(implicit + F: Sync[F], + S: ContextShift[F] ): Endpoint[F, Buf] = Endpoint.fromFile[F](file) /** - * An alias for [[Endpoint.classpathAsset]]. - */ + * An alias for [[Endpoint.classpathAsset]]. + */ def classpathAsset(path: String)(implicit F: Sync[F], S: ContextShift[F]): Endpoint[F, Buf] = Endpoint.classpathAsset[F](path) /** - * An alias for [[Endpoint.classpathAsset]]. - */ + * An alias for [[Endpoint.classpathAsset]]. + */ def filesystemAsset(path: String)(implicit F: Sync[F], S: ContextShift[F]): Endpoint[F, Buf] = Endpoint.filesystemAsset[F](path) /** - * An alias for [[Endpoint.root]]. - */ + * An alias for [[Endpoint.root]]. + */ def root(implicit F: Sync[F]): Endpoint[F, Request] = Endpoint.root[F] /** - * An alias for [[Endpoint.pathAny]]. - */ + * An alias for [[Endpoint.pathAny]]. + */ def pathAny(implicit F: Applicative[F]): Endpoint[F, HNil] = Endpoint.pathAny[F] /** - * An alias for [[Endpoint.pathEmpty]]. - */ + * An alias for [[Endpoint.pathEmpty]]. + */ def pathEmpty(implicit F: Applicative[F]): Endpoint[F, HNil] = Endpoint.pathEmpty[F] /** - * An alias for [[Endpoint.path]]. - */ + * An alias for [[Endpoint.path]]. + */ def path[A: DecodePath: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.path[F, A] /** - * An alias for [[Endpoint.paths]]. - */ + * An alias for [[Endpoint.paths]]. + */ def paths[A: DecodePath: ClassTag](implicit F: Sync[F]): Endpoint[F, List[A]] = Endpoint.paths[F, A] /** - * An alias for [[Endpoint.path]]. - * - * @note This method is implicit such that an implicit conversion `String => Endpoint[F, HNil]` - * works. - */ + * An alias for [[Endpoint.path]]. + * + * @note This method is implicit such that an implicit conversion `String => Endpoint[F, HNil]` + * works. + */ implicit def path(s: String)(implicit F: Sync[F]): Endpoint[F, HNil] = Endpoint.path[F](s) /** - * An alias for [[Endpoint.get]]. - */ + * An alias for [[Endpoint.get]]. + */ def get[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.get[F, A](e) /** - * An alias for [[Endpoint.post]]. - */ + * An alias for [[Endpoint.post]]. + */ def post[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.post[F, A](e) /** - * An alias for [[Endpoint.patch]]. - */ + * An alias for [[Endpoint.patch]]. + */ def patch[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.patch[F, A](e) /** - * An alias for [[Endpoint.delete]]. - */ + * An alias for [[Endpoint.delete]]. + */ def delete[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.delete[F, A](e) /** - * An alias for [[Endpoint.head]]. - */ + * An alias for [[Endpoint.head]]. + */ def head[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.head[F, A](e) /** - * An alias for [[Endpoint.options]]. - */ + * An alias for [[Endpoint.options]]. + */ def options[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.options[F, A](e) /** - * An alias for [[Endpoint.put]]. - */ + * An alias for [[Endpoint.put]]. + */ def put[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.put[F, A](e) /** - * An alias for [[Endpoint.trace]]. - */ + * An alias for [[Endpoint.trace]]. + */ def trace[A](e: Endpoint[F, A]): Endpoint.Mappable[F, A] = Endpoint.trace[F, A](e) /** - * An alias for [[Endpoint.header]]. - */ + * An alias for [[Endpoint.header]]. + */ def header[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.header[F, A](name) /** - * An alias for [[Endpoint.headerOption]]. - */ + * An alias for [[Endpoint.headerOption]]. + */ def headerOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.headerOption[F, A](name) /** - * An alias for [[Endpoint.binaryBodyOption]]. - */ + * An alias for [[Endpoint.binaryBodyOption]]. + */ def binaryBodyOption(implicit F: Sync[F]): Endpoint[F, Option[Array[Byte]]] = Endpoint.binaryBodyOption[F] /** - * An alias for [[Endpoint.binaryBody]]. - */ + * An alias for [[Endpoint.binaryBody]]. + */ def binaryBody(implicit F: Sync[F]): Endpoint[F, Array[Byte]] = Endpoint.binaryBody[F] /** - * An alias for [[Endpoint.stringBodyOption]]. - */ + * An alias for [[Endpoint.stringBodyOption]]. + */ def stringBodyOption(implicit F: Sync[F]): Endpoint[F, Option[String]] = Endpoint.stringBodyOption[F] /** - * An alias for [[Endpoint.stringBody]]. - */ + * An alias for [[Endpoint.stringBody]]. + */ def stringBody(implicit F: Sync[F]): Endpoint[F, String] = Endpoint.stringBody[F] /** - * An alias for [[Endpoint.bodyOption]]. - */ + * An alias for [[Endpoint.bodyOption]]. + */ def bodyOption[A: ClassTag, CT](implicit F: Sync[F], D: Decode.Dispatchable[A, CT]): Endpoint[F, Option[A]] = Endpoint.bodyOption[F, A, CT] /** - * An alias for [[Endpoint.body]]. - */ + * An alias for [[Endpoint.body]]. + */ def body[A: ClassTag, CT](implicit D: Decode.Dispatchable[A, CT], F: Sync[F]): Endpoint[F, A] = Endpoint.body[F, A, CT] /** - * An alias for [[Endpoint.jsonBody]]. - */ + * An alias for [[Endpoint.jsonBody]]. + */ def jsonBody[A: Decode.Json: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.jsonBody[F, A] /** - * An alias for [[Endpoint.jsonBodyOption]]. - */ + * An alias for [[Endpoint.jsonBodyOption]]. + */ def jsonBodyOption[A: Decode.Json: ClassTag](implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.jsonBodyOption[F, A] /** - * An alias for [[Endpoint.textBody]]. - */ + * An alias for [[Endpoint.textBody]]. + */ def textBody[A: Decode.Text: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.textBody[F, A] /** - * An alias for [[Endpoint.textBodyOption]]. - */ + * An alias for [[Endpoint.textBodyOption]]. + */ def textBodyOption[A: Decode.Text: ClassTag](implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.textBodyOption[F, A] /** - * An alias for [[Endpoint.binaryBodyStream]]. - */ + * An alias for [[Endpoint.binaryBodyStream]]. + */ def binaryBodyStream[S[_[_], _]](implicit - F: Sync[F], - LR: LiftReader[S, F] + F: Sync[F], + LR: LiftReader[S, F] ): Endpoint[F, S[F, Array[Byte]]] = Endpoint.binaryBodyStream[F, S] /** - * An alias for [[Endpoint.stringBodyStream]]. - */ + * An alias for [[Endpoint.stringBodyStream]]. + */ def stringBodyStream[S[_[_], _]](implicit - F: Sync[F], - LR: LiftReader[S, F] + F: Sync[F], + LR: LiftReader[S, F] ): Endpoint[F, S[F, String]] = Endpoint.stringBodyStream[F, S] /** - * An alias for [[Endpoint.bodyStream]]. - */ + * An alias for [[Endpoint.bodyStream]]. + */ def bodyStream[S[_[_], _], A, CT <: String](implicit - F: Sync[F], - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, CT] + F: Sync[F], + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, CT] ): Endpoint[F, S[F, A]] = Endpoint.bodyStream[F, S, A, CT] /** - * An alias for [[Endpoint.jsonBodyStream]]. - */ + * An alias for [[Endpoint.jsonBodyStream]]. + */ def jsonBodyStream[S[_[_], _], A](implicit - F: Sync[F], - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, Application.Json] + F: Sync[F], + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, Application.Json] ): Endpoint[F, S[F, A]] = Endpoint.jsonBodyStream[F, S, A] /** - * An alias for [[Endpoint.textBodyStream]]. - */ + * An alias for [[Endpoint.textBodyStream]]. + */ def textBodyStream[S[_[_], _], A](implicit - F: Sync[F], - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, Text.Plain] + F: Sync[F], + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, Text.Plain] ): Endpoint[F, S[F, A]] = Endpoint.textBodyStream[F, S, A] /** - * An alias for [[Endpoint.cookieOption]]. - */ + * An alias for [[Endpoint.cookieOption]]. + */ def cookieOption(name: String)(implicit F: Sync[F]): Endpoint[F, Option[Cookie]] = Endpoint.cookieOption[F](name) /** - * An alias for [[Endpoint.cookie]]. - */ + * An alias for [[Endpoint.cookie]]. + */ def cookie(name: String)(implicit F: Sync[F]): Endpoint[F, Cookie] = Endpoint.cookie[F](name) /** - * An alias for [[Endpoint.paramOption]]. - */ + * An alias for [[Endpoint.paramOption]]. + */ def paramOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.paramOption[F, A](name) /** - * An alias for [[Endpoint.param]]. - */ + * An alias for [[Endpoint.param]]. + */ def param[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.param[F, A](name) /** - * An alias for [[Endpoint.params]]. - */ + * An alias for [[Endpoint.params]]. + */ def params[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, List[A]] = Endpoint.params[F, A](name) /** - * An alias for [[Endpoint.paramsNel]]. - */ + * An alias for [[Endpoint.paramsNel]]. + */ def paramsNel[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, NonEmptyList[A]] = Endpoint.paramsNel[F, A](name) /** - * An alias for [[Endpoint.multipartFileUploadOption]]. - */ + * An alias for [[Endpoint.multipartFileUploadOption]]. + */ def multipartFileUploadOption(name: String)(implicit F: Sync[F]): Endpoint[F, Option[Multipart.FileUpload]] = Endpoint.multipartFileUploadOption[F](name) /** - * An alias for [[Endpoint.multipartFileUpload]]. - */ + * An alias for [[Endpoint.multipartFileUpload]]. + */ def multipartFileUpload(name: String)(implicit F: Sync[F]): Endpoint[F, Multipart.FileUpload] = Endpoint.multipartFileUpload[F](name) /** - * An alias for [[Endpoint.multipartFileUploads]]. - */ + * An alias for [[Endpoint.multipartFileUploads]]. + */ def multipartFileUploads(name: String)(implicit F: Sync[F]): Endpoint[F, List[Multipart.FileUpload]] = Endpoint.multipartFileUploads[F](name) /** - * An alias for [[Endpoint.multipartFileUploadsNel]]. - */ + * An alias for [[Endpoint.multipartFileUploadsNel]]. + */ def multipartFileUploadsNel(name: String)(implicit F: Sync[F]): Endpoint[F, NonEmptyList[Multipart.FileUpload]] = Endpoint.multipartFileUploadsNel[F](name) /** - * An alias for [[Endpoint.multipartAttribute]]. - */ + * An alias for [[Endpoint.multipartAttribute]]. + */ def multipartAttribute[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.multipartAttribute[F, A](name) /** - * An alias for [[Endpoint.multipartAttributeOption]]. - */ + * An alias for [[Endpoint.multipartAttributeOption]]. + */ def multipartAttributeOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.multipartAttributeOption[F, A](name) /** - * An alias for [[Endpoint.multipartAttributes]]. - */ + * An alias for [[Endpoint.multipartAttributes]]. + */ def multipartAttributes[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, List[A]] = Endpoint.multipartAttributes[F, A](name) /** - * An alias for [[Endpoint.multipartAttributesNel]]. - */ + * An alias for [[Endpoint.multipartAttributesNel]]. + */ def multipartAttributesNel[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, NonEmptyList[A]] = Endpoint.multipartAttributesNel[F, A](name) } diff --git a/core/src/main/scala/io/finch/EndpointResult.scala b/core/src/main/scala/io/finch/EndpointResult.scala index 093afbb63..34c2d1178 100644 --- a/core/src/main/scala/io/finch/EndpointResult.scala +++ b/core/src/main/scala/io/finch/EndpointResult.scala @@ -1,57 +1,55 @@ package io.finch +import scala.concurrent.duration.Duration + import cats.Id import cats.effect.Effect import com.twitter.finagle.http.Method import com.twitter.util._ -import scala.concurrent.duration.Duration /** - * A result returned from an [[Endpoint]]. This models `Option[(Input, Future[Output])]` and - * represents two cases: - * - * - Endpoint is matched (think of 200). - * - Endpoint is not matched (think of 404, 405, etc). - * - * In its current state, `EndpointResult.NotMatched` represented with two cases: - * - * - `EndpointResult.NotMatched` (very generic result usually indicating 404) - * - `EndpointResult.NotMatched.MethodNotAllowed` (indicates 405) - * - */ + * A result returned from an [[Endpoint]]. This models `Option[(Input, Future[Output])]` and + * represents two cases: + * + * - Endpoint is matched (think of 200). + * - Endpoint is not matched (think of 404, 405, etc). + * + * In its current state, `EndpointResult.NotMatched` represented with two cases: + * + * - `EndpointResult.NotMatched` (very generic result usually indicating 404) + * - `EndpointResult.NotMatched.MethodNotAllowed` (indicates 405) + */ sealed abstract class EndpointResult[F[_], +A] { /** - * Whether the [[Endpoint]] is matched on a given [[Input]]. - */ + * Whether the [[Endpoint]] is matched on a given [[Input]]. + */ final def isMatched: Boolean = this match { case EndpointResult.Matched(_, _, _) => true - case _ => false + case _ => false } /** - * Returns the remainder of the [[Input]] after an [[Endpoint]] is matched. - */ + * Returns the remainder of the [[Input]] after an [[Endpoint]] is matched. + */ final def remainder: Option[Input] = this match { case EndpointResult.Matched(rem, _, _) => Some(rem) - case _ => None + case _ => None } /** - * Returns the [[Trace]] if an [[Endpoint]] is matched. - */ + * Returns the [[Trace]] if an [[Endpoint]] is matched. + */ final def trace: Option[Trace] = this match { case EndpointResult.Matched(_, trc, _) => Some(trc) - case _ => None + case _ => None } def awaitOutput(d: Duration = Duration.Inf)(implicit F: Effect[F]): Option[Either[Throwable, Output[A]]] = this match { case EndpointResult.Matched(_, _, out) => - try { - F.toIO(out).unsafeRunTimed(d) match { - case Some(a) => Some(Right(a)) - case _ => Some(Left(new TimeoutException(s"Output wasn't returned in time: $d"))) - } + try F.toIO(out).unsafeRunTimed(d) match { + case Some(a) => Some(Right(a)) + case _ => Some(Left(new TimeoutException(s"Output wasn't returned in time: $d"))) } catch { case e: Throwable => Some(Left(e)) } @@ -64,10 +62,10 @@ sealed abstract class EndpointResult[F[_], +A] { case Left(ex) => throw ex } - def awaitValue(d: Duration = Duration.Inf)(implicit F: Effect[F]): Option[Either[Throwable, A]]= + def awaitValue(d: Duration = Duration.Inf)(implicit F: Effect[F]): Option[Either[Throwable, A]] = awaitOutput(d).map { case Right(oa) => Right(oa.value) - case Left(ob) => Left(ob) + case Left(ob) => Left(ob) } def awaitValueUnsafe(d: Duration = Duration.Inf)(implicit F: Effect[F]): Option[A] = @@ -77,9 +75,9 @@ sealed abstract class EndpointResult[F[_], +A] { object EndpointResult { final case class Matched[F[_], A]( - rem: Input, - trc: Trace, - out: F[Output[A]] + rem: Input, + trc: Trace, + out: F[Output[A]] ) extends EndpointResult[F, A] abstract class NotMatched[F[_]] extends EndpointResult[F, Nothing] @@ -93,11 +91,11 @@ object EndpointResult { implicit class EndpointResultOps[F[_], A](val self: EndpointResult[F, A]) extends AnyVal { /** - * Returns the [[Output]] if an [[Endpoint]] is matched. - */ + * Returns the [[Output]] if an [[Endpoint]] is matched. + */ final def output: Option[F[Output[A]]] = self match { case EndpointResult.Matched(_, _, out) => Some(out) - case _ => None + case _ => None } } } diff --git a/core/src/main/scala/io/finch/Error.scala b/core/src/main/scala/io/finch/Error.scala index 549c7bbe0..268f99e6d 100644 --- a/core/src/main/scala/io/finch/Error.scala +++ b/core/src/main/scala/io/finch/Error.scala @@ -1,37 +1,38 @@ package io.finch -import cats.{Eq, Show} -import cats.data.NonEmptyList -import cats.syntax.eq._ -import io.finch.items.RequestItem import scala.compat.Platform.EOL import scala.reflect.ClassTag import scala.util.control.NoStackTrace +import cats.data.NonEmptyList +import cats.syntax.eq._ +import cats.{Eq, Show} +import io.finch.items.RequestItem + /** - * A single error from an [[Endpoint]]. - * - * This indicates that one of the Finch's built-in components failed. This includes, but not - * limited by: - * - * - reading a required param, body, header, etc. - * - parsing a string-based endpoint with `.as[T]` combinator - * - validating an endpoint with `.should` or `shouldNot` combinators - */ + * A single error from an [[Endpoint]]. + * + * This indicates that one of the Finch's built-in components failed. This includes, but not + * limited by: + * + * - reading a required param, body, header, etc. + * - parsing a string-based endpoint with `.as[T]` combinator + * - validating an endpoint with `.should` or `shouldNot` combinators + */ sealed abstract class Error extends Exception with NoStackTrace /** - * Multiple errors from an [[Endpoint]]. - * - * This type of error indicates that an endpoint is able to accumulate multiple [[Error]]s - * into a single instance of [[Errors]] that embeds a non-empty list. - * - * Error accumulation happens as part of the `.product` (or `adjoin`, `::`) combinator. - */ + * Multiple errors from an [[Endpoint]]. + * + * This type of error indicates that an endpoint is able to accumulate multiple [[Error]]s + * into a single instance of [[Errors]] that embeds a non-empty list. + * + * Error accumulation happens as part of the `.product` (or `adjoin`, `::`) combinator. + */ case class Errors(errors: NonEmptyList[Error]) extends Exception with NoStackTrace { override def getMessage: String = "One or more errors reading request:" + - errors.map(_.getMessage).toList.mkString(EOL + " ", EOL + " ","") + errors.map(_.getMessage).toList.mkString(EOL + " ", EOL + " ", "") } object Error { @@ -45,39 +46,38 @@ object Error { } /** - * An exception that indicates a required request item (''header'', ''param'', ''cookie'', - * ''body'') was missing in the request. - * - * @param item the missing request item - */ + * An exception that indicates a required request item (''header'', ''param'', ''cookie'', + * ''body'') was missing in the request. + * + * @param item the missing request item + */ final case class NotPresent(item: RequestItem) extends Error { override def getMessage: String = s"Required ${item.description} not present in the request." } /** - * An exception that indicates a broken [[ValidationRule]] on the request item. - * - * @param item the invalid request item - * @param rule the rule description - */ + * An exception that indicates a broken [[ValidationRule]] on the request item. + * + * @param item the invalid request item + * @param rule the rule description + */ final case class NotValid(item: RequestItem, rule: String) extends Error { override def getMessage: String = s"Validation failed: ${item.description} $rule." } /** - * An exception that indicates that a request item could be parsed. - * - * @param item the invalid request item - * @param targetType the type the item should be converted into - * @param cause the cause of the parsing error - */ - final case class NotParsed(item: RequestItem, targetType: ClassTag[_], cause: Throwable) - extends Error { + * An exception that indicates that a request item could be parsed. + * + * @param item the invalid request item + * @param targetType the type the item should be converted into + * @param cause the cause of the parsing error + */ + final case class NotParsed(item: RequestItem, targetType: ClassTag[_], cause: Throwable) extends Error { override def getMessage: String = { // Note: https://issues.scala-lang.org/browse/SI-2034 val className = targetType.runtimeClass.getName - val simpleName = className.substring(className.lastIndexOf(".")+1) + val simpleName = className.substring(className.lastIndexOf(".") + 1) s"${item.description} cannot be converted to ${simpleName}: ${cause.getMessage}." } diff --git a/core/src/main/scala/io/finch/Input.scala b/core/src/main/scala/io/finch/Input.scala index 9fa92c4fa..aca632477 100644 --- a/core/src/main/scala/io/finch/Input.scala +++ b/core/src/main/scala/io/finch/Input.scala @@ -2,56 +2,56 @@ package io.finch import java.nio.charset.{Charset, StandardCharsets} +import scala.collection.mutable.ListBuffer + import cats.Eq import cats.effect.Effect import com.twitter.finagle.http.{Method, Request, RequestBuilder} import com.twitter.io.{Buf, Reader} -import scala.collection.mutable.ListBuffer import shapeless.Witness - /** - * An input for [[Endpoint]] that glues two individual pieces together: - * - * - Finagle's [[Request]] needed for evaluating (e.g., `body`, `param`) - * - Finch's route (represented as `Seq[String]`) needed for matching (e.g., `path`) - */ + * An input for [[Endpoint]] that glues two individual pieces together: + * + * - Finagle's [[Request]] needed for evaluating (e.g., `body`, `param`) + * - Finch's route (represented as `Seq[String]`) needed for matching (e.g., `path`) + */ final case class Input(request: Request, route: List[String]) { /** - * Returns the new `Input` wrapping a given `route`. - */ + * Returns the new `Input` wrapping a given `route`. + */ def withRoute(route: List[String]): Input = Input(request, route) /** - * Returns the new `Input` wrapping a given payload. This requires the content-type as a first - * type parameter (won't be inferred). - * - * ``` - * import io.finch._, io.circe._ - * - * val text = Input.post("/").withBody[Text.Plain]("Text Body") - * val json = Input.post("/").withBody[Application.Json](Map("json" -> "object")) - *``` - * - * Also possible to create chunked inputs passing a stream as an argument. - * - *``` - * import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator - * import io.finch.circe._, io.circe.generic.auto._ - * - * val enumerateText = Enumerator.enumerate[IO, String]("foo", "bar") - * val text = Input.post("/").withBody[Text.Plain](enumerateText) - * - * val enumerateJson = Enumerate.enumerate[IO, Map[String, String]](Map("foo" - "bar")) - * val json = Input.post("/").withBody[Application.Json](enumerateJson) - *``` - */ + * Returns the new `Input` wrapping a given payload. This requires the content-type as a first + * type parameter (won't be inferred). + * + * ``` + * import io.finch._, io.circe._ + * + * val text = Input.post("/").withBody[Text.Plain]("Text Body") + * val json = Input.post("/").withBody[Application.Json](Map("json" -> "object")) + * ``` + * + * Also possible to create chunked inputs passing a stream as an argument. + * + * ``` + * import io.finch._, io.finch.iteratee._, cats.effect.IO, io.iteratee.Enumerator + * import io.finch.circe._, io.circe.generic.auto._ + * + * val enumerateText = Enumerator.enumerate[IO, String]("foo", "bar") + * val text = Input.post("/").withBody[Text.Plain](enumerateText) + * + * val enumerateJson = Enumerate.enumerate[IO, Map[String, String]](Map("foo" - "bar")) + * val json = Input.post("/").withBody[Application.Json](enumerateJson) + * ``` + */ def withBody[CT <: String]: Input.Body[CT] = new Input.Body[CT](this) /** - * Returns the new `Input` with `headers` amended. - */ + * Returns the new `Input` with `headers` amended. + */ def withHeaders(headers: (String, String)*): Input = { val copied = Input.copyRequest(request) headers.foreach { case (k, v) => copied.headerMap.set(k, v) } @@ -60,29 +60,26 @@ final case class Input(request: Request, route: List[String]) { } /** - * Returns the new `Input` wrapping a given `application/x-www-form-urlencoded` payload. - * - * @note In addition to media type, this will also set charset to UTF-8. - */ + * Returns the new `Input` wrapping a given `application/x-www-form-urlencoded` payload. + * + * @note In addition to media type, this will also set charset to UTF-8. + */ def withForm(params: (String, String)*): Input = { - val postRequest: Request = RequestBuilder() - .addFormElement(params: _*) - .url("http://localhost") - .buildFormPost() + val postRequest: Request = RequestBuilder().addFormElement(params: _*).url("http://localhost").buildFormPost() withBody[Application.WwwFormUrlencoded](postRequest.content, StandardCharsets.UTF_8) } } /** - * Creates an input for [[Endpoint]] from [[Request]]. - */ + * Creates an input for [[Endpoint]] from [[Request]]. + */ object Input { - private final def copyRequest(from: Request): Request = + final private def copyRequest(from: Request): Request = copyRequestWithReader(from, from.reader) - private final def copyRequestWithReader(from: Request, reader: Reader[Buf]): Request = { + final private def copyRequestWithReader(from: Request, reader: Reader[Buf]): Request = { val to = Request(from.version, from.method, from.uri, reader) to.setChunked(from.isChunked) to.content = from.content @@ -92,14 +89,15 @@ object Input { } /** - * A helper class that captures the `Content-Type` of the payload. - */ + * A helper class that captures the `Content-Type` of the payload. + */ class Body[CT <: String](i: Input) { def apply[A](body: A)(implicit e: Encode.Aux[A, CT], w: Witness.Aux[CT]): Input = apply[A](body, StandardCharsets.UTF_8) def apply[A](body: A, charset: Charset)(implicit - e: Encode.Aux[A, CT], W: Witness.Aux[CT] + e: Encode.Aux[A, CT], + W: Witness.Aux[CT] ): Input = { val content = e(body, charset) val copied = copyRequest(i.request) @@ -114,13 +112,14 @@ object Input { } def apply[F[_]: Effect, S[_[_], _], A](s: S[F, A])(implicit - S: EncodeStream.Aux[F, S, A, CT], W: Witness.Aux[CT] + S: EncodeStream.Aux[F, S, A, CT], + W: Witness.Aux[CT] ): Input = apply[F, S, A](s, StandardCharsets.UTF_8) def apply[F[_], S[_[_], _], A](s: S[F, A], charset: Charset)(implicit - F: Effect[F], - S: EncodeStream.Aux[F, S, A, CT], - W: Witness.Aux[CT] + F: Effect[F], + S: EncodeStream.Aux[F, S, A, CT], + W: Witness.Aux[CT] ): Input = { val content = F.toIO(S(s, charset)).unsafeRunSync() val copied = copyRequestWithReader(i.request, content) @@ -137,8 +136,8 @@ object Input { implicit val inputEq: Eq[Input] = Eq.fromUniversalEquals /** - * Creates an [[Input]] from a given [[Request]]. - */ + * Creates an [[Input]] from a given [[Request]]. + */ def fromRequest(req: Request): Input = { val p = req.path @@ -165,31 +164,31 @@ object Input { } /** - * Creates a `GET` input with a given query string (represented as `params`). - */ + * Creates a `GET` input with a given query string (represented as `params`). + */ def get(path: String, params: (String, String)*): Input = fromRequest(Request(Method.Get, Request.queryString(path, params: _*))) /** - * Creates a `PUT` input with a given query string (represented as `params`). - */ + * Creates a `PUT` input with a given query string (represented as `params`). + */ def put(path: String, params: (String, String)*): Input = fromRequest(Request(Method.Put, Request.queryString(path, params: _*))) /** - * Creates a `PATCH` input with a given query string (represented as `params`). - */ + * Creates a `PATCH` input with a given query string (represented as `params`). + */ def patch(path: String, params: (String, String)*): Input = fromRequest(Request(Method.Patch, Request.queryString(path, params: _*))) /** - * Creates a `DELETE` input with a given query string (represented as `params`). - */ + * Creates a `DELETE` input with a given query string (represented as `params`). + */ def delete(path: String, params: (String, String)*): Input = fromRequest(Request(Method.Delete, Request.queryString(path, params: _*))) /** - * Creates a `POST` input with empty payload. - */ + * Creates a `POST` input with empty payload. + */ def post(path: String): Input = fromRequest(Request(Method.Post, path)) } diff --git a/core/src/main/scala/io/finch/Output.scala b/core/src/main/scala/io/finch/Output.scala index b1e19f863..0a3efe4ea 100644 --- a/core/src/main/scala/io/finch/Output.scala +++ b/core/src/main/scala/io/finch/Output.scala @@ -1,195 +1,192 @@ package io.finch +import java.nio.charset.{Charset, StandardCharsets} + import cats.{Applicative, Eq} import com.twitter.finagle.http.{Cookie, Response, Status} -import java.nio.charset.{Charset, StandardCharsets} /** - * An output of [[Endpoint]]. - */ + * An output of [[Endpoint]]. + */ sealed trait Output[+A] { self => /** - * The status code of this [[Output]]. - */ + * The status code of this [[Output]]. + */ def status: Status /** - * The header map of this [[Output]]. - */ + * The header map of this [[Output]]. + */ def headers: Map[String, String] /** - * The cookie list of this [[Output]]. - */ + * The cookie list of this [[Output]]. + */ def cookies: List[Cookie] /** - * The charset of this [[Output]]. - */ + * The charset of this [[Output]]. + */ def charset: Option[Charset] /** - * Returns the payload value of this [[Output]] or throws an exception. - */ + * Returns the payload value of this [[Output]] or throws an exception. + */ def value: A final def map[B](fn: A => B): Output[B] = this match { case p: Output.Payload[A] => p.withValue(fn(p.value)) - case f: Output.Failure => f - case e: Output.Empty => e + case f: Output.Failure => f + case e: Output.Empty => e } final def flatMap[B](fn: A => Output[B]): Output[B] = this match { case p: Output.Payload[A] => fn(p.value).withCookies(p.cookies).withHeaders(p.headers) - case f: Output.Failure => f - case e: Output.Empty => e + case f: Output.Failure => f + case e: Output.Empty => e } final def traverse[F[_], B](fn: A => F[B])(implicit F: Applicative[F]): F[Output[B]] = this match { case p: Output.Payload[A] => F.map(fn(p.value))(b => p.withValue(b)) - case f: Output.Failure => F.pure(f) - case e: Output.Empty => F.pure(e) + case f: Output.Failure => F.pure(f) + case e: Output.Empty => F.pure(e) } final def traverseFlatten[F[_], B](fn: A => F[Output[B]])(implicit F: Applicative[F]): F[Output[B]] = this match { case p: Output.Payload[A] => F.map(fn(p.value))(ob => ob.withHeaders(self.headers).withCookies(self.cookies)) case f: Output.Failure => F.pure(f) - case e: Output.Empty => F.pure(e) + case e: Output.Empty => F.pure(e) } /** - * Overrides `charset` of this [[Output]]. - */ + * Overrides `charset` of this [[Output]]. + */ final def withCharset(charset: Charset): Output[A] = copy(charset = Some(charset)) /** - * Overrides the `status` code of this [[Output]]. - */ + * Overrides the `status` code of this [[Output]]. + */ final def withStatus(status: Status): Output[A] = copy(status = status) /** - * Adds given `headers` to this [[Output]]. - */ + * Adds given `headers` to this [[Output]]. + */ final def withHeaders(headers: Map[String, String]): Output[A] = if (headers.isEmpty) this else copy(headers = self.headers ++ headers) /** - * Adds given `cookies` to this [[Output]]. - */ + * Adds given `cookies` to this [[Output]]. + */ final def withCookies(cookies: List[Cookie]): Output[A] = if (cookies.isEmpty) this else copy(cookies = self.cookies ++ cookies) /** - * Adds a given `header` to this [[Output]]. - */ + * Adds a given `header` to this [[Output]]. + */ final def withHeader(header: (String, String)): Output[A] = withHeaders(Map(header)) /** - * Adds a given `cookie` to this [[Output]]. - */ + * Adds a given `cookie` to this [[Output]]. + */ final def withCookie(cookie: Cookie): Output[A] = withCookies(List(cookie)) - protected def copy(status: Status = self.status, - charset: Option[Charset] = self.charset, - headers: Map[String, String] = self.headers, - cookies: List[Cookie] = self.cookies): Output[A] + protected def copy( + status: Status = self.status, + charset: Option[Charset] = self.charset, + headers: Map[String, String] = self.headers, + cookies: List[Cookie] = self.cookies + ): Output[A] } object Output { /** - * Creates a successful [[Output]] that wraps a payload `value` with given `status`. - */ + * Creates a successful [[Output]] that wraps a payload `value` with given `status`. + */ final def payload[A](value: A, status: Status = Status.Ok): Output[A] = Payload(value, status) /** - * Creates a failure [[Output]] that wraps an exception `cause` causing this. - */ + * Creates a failure [[Output]] that wraps an exception `cause` causing this. + */ final def failure[A](cause: Exception, status: Status = Status.BadRequest): Output[A] = Failure(cause, status) /** - * Creates an empty [[Output]] of given `status`. - */ + * Creates an empty [[Output]] of given `status`. + */ final def empty[A](status: Status): Output[A] = Empty(status) /** - * Creates a unit/empty [[Output]] (i.e., `Output[Unit]`) of given `status`. - */ + * Creates a unit/empty [[Output]] (i.e., `Output[Unit]`) of given `status`. + */ final def unit(status: Status): Output[Unit] = empty(status) /** - * An [[Output]] with `None` as a payload. - */ + * An [[Output]] with `None` as a payload. + */ val None: Output[Option[Nothing]] = Output.payload(Option.empty[Nothing]) /** - * An [[Output]] with [[shapeless.HNil]] as a payload. - */ + * An [[Output]] with [[shapeless.HNil]] as a payload. + */ val HNil: Output[shapeless.HNil] = Output.payload(shapeless.HNil) /** - * A successful [[Output]] that captures a payload `value`. - */ - private[finch] final case class Payload[A]( + * A successful [[Output]] that captures a payload `value`. + */ + final private[finch] case class Payload[A]( value: A, status: Status = Status.Ok, charset: Option[Charset] = Option.empty, headers: Map[String, String] = Map.empty[String, String], - cookies: List[Cookie] = List.empty[Cookie]) extends Output[A] { self => + cookies: List[Cookie] = List.empty[Cookie] + ) extends Output[A] { self => def withValue[B](value: B): Payload[B] = Payload(value, status, charset, headers, cookies) - protected def copy( - status: Status, - charset: Option[Charset], - headers: Map[String, String], - cookies: List[Cookie]): Output[A] = Payload(value, status, charset, headers, cookies) + protected def copy(status: Status, charset: Option[Charset], headers: Map[String, String], cookies: List[Cookie]): Output[A] = + Payload(value, status, charset, headers, cookies) } /** - * A failure [[Output]] that captures an [[Exception]] explaining why it's not a payload - * or an empty response. - */ - private[finch] final case class Failure( + * A failure [[Output]] that captures an [[Exception]] explaining why it's not a payload + * or an empty response. + */ + final private[finch] case class Failure( cause: Exception, status: Status = Status.BadRequest, charset: Option[Charset] = Option.empty, headers: Map[String, String] = Map.empty[String, String], - cookies: List[Cookie] = List.empty[Cookie]) extends Output[Nothing] { + cookies: List[Cookie] = List.empty[Cookie] + ) extends Output[Nothing] { def value: Nothing = throw cause - protected def copy( - status: Status, - charset: Option[Charset], - headers: Map[String, String], - cookies: List[Cookie]): Output[Nothing] = Failure(cause, status, charset, headers, cookies) + protected def copy(status: Status, charset: Option[Charset], headers: Map[String, String], cookies: List[Cookie]): Output[Nothing] = + Failure(cause, status, charset, headers, cookies) } /** - * An empty [[Output]] that does not capture any payload. - */ - private[finch] final case class Empty( + * An empty [[Output]] that does not capture any payload. + */ + final private[finch] case class Empty( status: Status, charset: Option[Charset] = Option.empty, headers: Map[String, String] = Map.empty[String, String], - cookies: List[Cookie] = List.empty[Cookie]) extends Output[Nothing] { + cookies: List[Cookie] = List.empty[Cookie] + ) extends Output[Nothing] { def value: Nothing = throw new IllegalStateException("empty output") - protected def copy( - status: Status, - charset: Option[Charset], - headers: Map[String, String], - cookies: List[Cookie]): Output[Nothing] = Empty(status, charset, headers, cookies) + protected def copy(status: Status, charset: Option[Charset], headers: Map[String, String], cookies: List[Cookie]): Output[Nothing] = + Empty(status, charset, headers, cookies) } implicit def outputEq[A]: Eq[Output[A]] = Eq.fromUniversalEquals @@ -197,17 +194,17 @@ object Output { implicit class OutputOps[A](val o: Output[A]) extends AnyVal { /** - * Converts this [[Output]] to the HTTP response of the given `version`. - */ + * Converts this [[Output]] to the HTTP response of the given `version`. + */ def toResponse[F[_], CT](implicit - F: Applicative[F], - tr: ToResponse.Aux[F, A, CT], - tre: ToResponse.Aux[F, Exception, CT] + F: Applicative[F], + tr: ToResponse.Aux[F, A, CT], + tre: ToResponse.Aux[F, Exception, CT] ): F[Response] = { val rep0 = o match { case p: Output.Payload[A] => tr(p.value, p.charset.getOrElse(StandardCharsets.UTF_8)) - case f: Output.Failure => tre(f.cause, f.charset.getOrElse(StandardCharsets.UTF_8)) - case _: Output.Empty => F.pure(Response()) + case f: Output.Failure => tre(f.cause, f.charset.getOrElse(StandardCharsets.UTF_8)) + case _: Output.Empty => F.pure(Response()) } F.map(rep0) { rep => diff --git a/core/src/main/scala/io/finch/Outputs.scala b/core/src/main/scala/io/finch/Outputs.scala index 6aa6f6430..ef9bd71d1 100644 --- a/core/src/main/scala/io/finch/Outputs.scala +++ b/core/src/main/scala/io/finch/Outputs.scala @@ -8,54 +8,54 @@ trait Outputs { // is assembled. // 2xx - def Ok[A](a: A): Output[A] = Output.payload(a, Status.Ok) // 200 - def Created[A](a: A): Output[A] = Output.payload(a, Status.Created) // 201 - def Accepted[A]: Output[A] = Output.empty(Status.Accepted) // 202 - def NoContent[A]: Output[A] = Output.empty(Status.NoContent) // 204 + def Ok[A](a: A): Output[A] = Output.payload(a, Status.Ok) // 200 + def Created[A](a: A): Output[A] = Output.payload(a, Status.Created) // 201 + def Accepted[A]: Output[A] = Output.empty(Status.Accepted) // 202 + def NoContent[A]: Output[A] = Output.empty(Status.NoContent) // 204 // 4xx def BadRequest(cause: Exception): Output[Nothing] = Output.failure(cause, Status.BadRequest) //400 def Unauthorized(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.Unauthorized) //401 + Output.failure(cause, Status.Unauthorized) //401 def PaymentRequired(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.PaymentRequired) //402 + Output.failure(cause, Status.PaymentRequired) //402 - def Forbidden(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Forbidden) //403 - def NotFound(cause: Exception): Output[Nothing] = Output.failure(cause, Status.NotFound) //404 + def Forbidden(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Forbidden) //403 + def NotFound(cause: Exception): Output[Nothing] = Output.failure(cause, Status.NotFound) //404 def MethodNotAllowed(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.MethodNotAllowed) //405 + Output.failure(cause, Status.MethodNotAllowed) //405 def NotAcceptable(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.NotAcceptable) //406 + Output.failure(cause, Status.NotAcceptable) //406 def RequestTimeout(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.RequestTimeout) //408 - def Conflict(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Conflict) //409 - def Gone(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Gone) //410 + Output.failure(cause, Status.RequestTimeout) //408 + def Conflict(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Conflict) //409 + def Gone(cause: Exception): Output[Nothing] = Output.failure(cause, Status.Gone) //410 def LengthRequired(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.LengthRequired) //411 + Output.failure(cause, Status.LengthRequired) //411 def PreconditionFailed(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.PreconditionFailed) //412 + Output.failure(cause, Status.PreconditionFailed) //412 def RequestEntityTooLarge(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.RequestEntityTooLarge) //413 + Output.failure(cause, Status.RequestEntityTooLarge) //413 def RequestedRangeNotSatisfiable(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.RequestedRangeNotSatisfiable) //416 + Output.failure(cause, Status.RequestedRangeNotSatisfiable) //416 def EnhanceYourCalm(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.EnhanceYourCalm) //420 + Output.failure(cause, Status.EnhanceYourCalm) //420 def UnprocessableEntity(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.UnprocessableEntity) //422 + Output.failure(cause, Status.UnprocessableEntity) //422 def TooManyRequests(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.TooManyRequests) //429 + Output.failure(cause, Status.TooManyRequests) //429 // 5xx def InternalServerError(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.InternalServerError) //500 + Output.failure(cause, Status.InternalServerError) //500 def NotImplemented(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.NotImplemented) //501 + Output.failure(cause, Status.NotImplemented) //501 def BadGateway(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.BadGateway) //502 + Output.failure(cause, Status.BadGateway) //502 def ServiceUnavailable(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.ServiceUnavailable) //503 + Output.failure(cause, Status.ServiceUnavailable) //503 def GatewayTimeout(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.GatewayTimeout) //504 + Output.failure(cause, Status.GatewayTimeout) //504 def InsufficientStorage(cause: Exception): Output[Nothing] = - Output.failure(cause, Status.InsufficientStorage) //507 + Output.failure(cause, Status.InsufficientStorage) //507 } diff --git a/core/src/main/scala/io/finch/ServerSentEvent.scala b/core/src/main/scala/io/finch/ServerSentEvent.scala index 3193bb6cf..ba906a447 100644 --- a/core/src/main/scala/io/finch/ServerSentEvent.scala +++ b/core/src/main/scala/io/finch/ServerSentEvent.scala @@ -1,14 +1,15 @@ package io.finch +import java.nio.charset.Charset + import cats.Show import com.twitter.io.Buf -import java.nio.charset.Charset case class ServerSentEvent[A]( - data: A, - id: Option[String] = None, - event: Option[String] = None, - retry: Option[Long] = None + data: A, + id: Option[String] = None, + event: Option[String] = None, + retry: Option[Long] = None ) object ServerSentEvent { @@ -16,7 +17,7 @@ object ServerSentEvent { private def text(s: String, cs: Charset) = Buf.ByteArray.Owned(s.getBytes(cs.name)) implicit def encodeEventStream[A](implicit - A: Show[A] + A: Show[A] ): Encode.Aux[ServerSentEvent[A], Text.EventStream] = new Encode[ServerSentEvent[A]] { type ContentType = Text.EventStream @@ -31,4 +32,3 @@ object ServerSentEvent { } } } - diff --git a/core/src/main/scala/io/finch/ToResponse.scala b/core/src/main/scala/io/finch/ToResponse.scala index 3dd389408..b7cb08fce 100644 --- a/core/src/main/scala/io/finch/ToResponse.scala +++ b/core/src/main/scala/io/finch/ToResponse.scala @@ -1,14 +1,16 @@ package io.finch -import cats.{Applicative, Functor} -import com.twitter.finagle.http.{Response, Status, Version} import java.nio.charset.Charset + import scala.annotation.implicitNotFound + +import cats.{Applicative, Functor} +import com.twitter.finagle.http.{Response, Status, Version} import shapeless._ /** - * Represents a conversion from `A` to [[Response]]. - */ + * Represents a conversion from `A` to [[Response]]. + */ trait ToResponse[F[_], A] { type ContentType @@ -37,13 +39,13 @@ trait ToResponseInstances { } implicit def responseToResponse[F[_], CT <: String](implicit - F: Applicative[F] + F: Applicative[F] ): Aux[F, Response, CT] = instance((r, _) => F.pure(r)) implicit def valueToResponse[F[_], A, CT <: String](implicit - F: Applicative[F], - A: Encode.Aux[A, CT], - CT: Witness.Aux[CT] + F: Applicative[F], + A: Encode.Aux[A, CT], + CT: Witness.Aux[CT] ): Aux[F, A, CT] = instance { (a, cs) => val buf = A(a, cs) val rep = Response(Version.Http11, Status.Ok) @@ -57,9 +59,9 @@ trait ToResponseInstances { } implicit def streamToResponse[F[_], S[_[_], _], A, CT <: String](implicit - F: Functor[F], - S: EncodeStream.Aux[F, S, A, CT], - CT: Witness.Aux[CT] + F: Functor[F], + S: EncodeStream.Aux[F, S, A, CT], + CT: Witness.Aux[CT] ): Aux[F, S[F, A], CT] = instance { (a, cs) => F.map(S(a, cs)) { stream => val rep = Response(Version.Http11, Status.Ok, stream) @@ -74,10 +76,10 @@ trait ToResponseInstances { object ToResponse extends ToResponseInstances { /** - * Enables server-driven content negotiation with client. - * - * Picks corresponding instance of `ToResponse` according to `Accept` header of a request - */ + * Enables server-driven content negotiation with client. + * + * Picks corresponding instance of `ToResponse` according to `Accept` header of a request + */ trait Negotiable[F[_], A, CT] { def apply(accept: List[Accept]): ToResponse.Aux[F, A, CT] } @@ -85,9 +87,9 @@ object ToResponse extends ToResponseInstances { object Negotiable { implicit def coproductToNegotiable[F[_], A, CTH <: String, CTT <: Coproduct](implicit - h: ToResponse.Aux[F, A, CTH], - t: Negotiable[F, A, CTT], - a: Accept.Matcher[CTH] + h: ToResponse.Aux[F, A, CTH], + t: Negotiable[F, A, CTT], + a: Accept.Matcher[CTH] ): Negotiable[F, A, CTH :+: CTT] = new Negotiable[F, A, CTH :+: CTT] { def apply(accept: List[Accept]): ToResponse.Aux[F, A, CTH :+: CTT] = if (accept.exists(_.matches[CTH])) h.asInstanceOf[ToResponse.Aux[F, A, CTH :+: CTT]] @@ -95,14 +97,14 @@ object ToResponse extends ToResponseInstances { } implicit def cnilToNegotiable[F[_], A, CTH <: String](implicit - tr: ToResponse.Aux[F, A, CTH] + tr: ToResponse.Aux[F, A, CTH] ): Negotiable[F, A, CTH :+: CNil] = new Negotiable[F, A, CTH :+: CNil] { def apply(accept: List[Accept]): ToResponse.Aux[F, A, CTH :+: CNil] = tr.asInstanceOf[ToResponse.Aux[F, A, CTH :+: CNil]] } implicit def singleToNegotiable[F[_], A, CT <: String](implicit - tr: ToResponse.Aux[F, A, CT] + tr: ToResponse.Aux[F, A, CT] ): Negotiable[F, A, CT] = new Negotiable[F, A, CT] { def apply(accept: List[Accept]): ToResponse.Aux[F, A, CT] = tr } @@ -120,18 +122,19 @@ object ToResponse extends ToResponseInstances { } implicit def cnilToResponse[F[_], CT <: String](implicit - F: Applicative[F] + F: Applicative[F] ): Aux[F, CNil, CT] = instance((_, _) => F.pure(Response(Version.Http10, Status.NotFound))) - implicit def cconsToResponse[F[_], L, R <: Coproduct, CT <: String]( - implicit trL: ToResponse.Aux[F, L, CT], fcR: Aux[F, R, CT] + implicit def cconsToResponse[F[_], L, R <: Coproduct, CT <: String](implicit + trL: ToResponse.Aux[F, L, CT], + fcR: Aux[F, R, CT] ): Aux[F, L :+: R, CT] = instance { case (Inl(h), cs) => trL(h, cs) case (Inr(t), cs) => fcR(t, cs) } } - implicit def coproductToResponse[F[_], C <: Coproduct, CT <: String]( - implicit fc: FromCoproduct.Aux[F, C, CT] + implicit def coproductToResponse[F[_], C <: Coproduct, CT <: String](implicit + fc: FromCoproduct.Aux[F, C, CT] ): Aux[F, C, CT] = fc } diff --git a/core/src/main/scala/io/finch/ToService.scala b/core/src/main/scala/io/finch/ToService.scala index 40cb5e473..e073b7a7b 100644 --- a/core/src/main/scala/io/finch/ToService.scala +++ b/core/src/main/scala/io/finch/ToService.scala @@ -11,22 +11,23 @@ import com.twitter.util.{Future, Promise} */ case class ToService[F[_]](compiled: Endpoint.Compiled[F])(implicit F: Effect[F]) extends Service[Request, Response] { def apply(request: Request): Future[Response] = { - val repF = compiled(request).flatMap { - case (trc, either) => - Trace.captureIfNeeded(trc) - F.fromEither(either) + val repF = compiled(request).flatMap { case (trc, either) => + Trace.captureIfNeeded(trc) + F.fromEither(either) } val rep = new Promise[Response] val run = (F match { case concurrent: ConcurrentEffect[F] => - (concurrent.runCancelable(repF) _).andThen(io => io.map(cancelToken => - rep.setInterruptHandler { - case _ => concurrent.toIO(cancelToken).unsafeRunAsyncAndForget() - } - )) + (concurrent.runCancelable(repF) _).andThen(io => + io.map(cancelToken => + rep.setInterruptHandler { case _ => + concurrent.toIO(cancelToken).unsafeRunAsyncAndForget() + } + ) + ) case e => e.runAsync(repF) _ }) { - case Left(t) => IO(rep.setException(t)) + case Left(t) => IO(rep.setException(t)) case Right(v) => IO(rep.setValue(v)) } diff --git a/core/src/main/scala/io/finch/Trace.scala b/core/src/main/scala/io/finch/Trace.scala index cda2d096e..ca68a1ac0 100644 --- a/core/src/main/scala/io/finch/Trace.scala +++ b/core/src/main/scala/io/finch/Trace.scala @@ -1,19 +1,20 @@ package io.finch -import com.twitter.util.Local import scala.annotation.tailrec import scala.collection.mutable.ListBuffer +import com.twitter.util.Local + /** - * Models a trace of a matched [[Endpoint]]. For example, `/hello/:name`. - * - * @note represented as a linked-list-like structure for efficiency. - */ + * Models a trace of a matched [[Endpoint]]. For example, `/hello/:name`. + * + * @note represented as a linked-list-like structure for efficiency. + */ sealed trait Trace { /** - * Concatenates this and `that` [[Trace]]s. - */ + * Concatenates this and `that` [[Trace]]s. + */ final def concat(that: Trace): Trace = { @tailrec def loop(from: Trace, last: Trace.Segment): Unit = from match { @@ -27,23 +28,24 @@ sealed trait Trace { this match { case Trace.Empty => that - case a @ Trace.Segment(_, _) => that match { - case Trace.Empty => a - case _ => - val result = Trace.Segment(a.path, Trace.Empty) - loop(a.next, result) - result - } + case a @ Trace.Segment(_, _) => + that match { + case Trace.Empty => a + case _ => + val result = Trace.Segment(a.path, Trace.Empty) + loop(a.next, result) + result + } } } /** - * Converts this [[Trace]] into a linked list of path segments. - */ + * Converts this [[Trace]] into a linked list of path segments. + */ final def toList: List[String] = { @tailrec def loop(from: Trace, to: ListBuffer[String]): List[String] = from match { - case Trace.Empty => to.toList + case Trace.Empty => to.toList case Trace.Segment(path, next) => loop(next, to += path) } @@ -55,7 +57,7 @@ sealed trait Trace { object Trace { private case object Empty extends Trace - private final case class Segment(path: String, var next: Trace) extends Trace + final private case class Segment(path: String, var next: Trace) extends Trace private class Capture(var trace: Trace) private val captureLocal = new Local[Capture] @@ -67,7 +69,7 @@ object Trace { var result = empty var current: Segment = null - def prepend(segment: Segment): Unit = { + def prepend(segment: Segment): Unit = if (result == empty) { result = segment current = segment @@ -75,7 +77,6 @@ object Trace { current.next = segment current = segment } - } var rs = r while (rs.nonEmpty) { @@ -87,29 +88,29 @@ object Trace { } /** - * Within a given context `fn`, capture the [[Trace]] instance under `Trace.captured` for each - * matched endpoint. - * - * Example: - * - * {{{ - * val foo = Endpoint.lift("foo").toService[Text.Plain] - * Trace.capture { foo(Request()).map(_ => Trace.captured) } - * }}} - */ + * Within a given context `fn`, capture the [[Trace]] instance under `Trace.captured` for each + * matched endpoint. + * + * Example: + * + * {{{ + * val foo = Endpoint.lift("foo").toService[Text.Plain] + * Trace.capture { foo(Request()).map(_ => Trace.captured) } + * }}} + */ def capture[A](fn: => A): A = captureLocal.let(new Capture(empty))(fn) /** - * Retrieve the captured [[Trace]] instance or [[empty]] when run outside of [[Trace.capture]] - * context. - */ + * Retrieve the captured [[Trace]] instance or [[empty]] when run outside of [[Trace.capture]] + * context. + */ def captured: Trace = captureLocal() match { case Some(c) => c.trace - case None => empty + case None => empty } private[finch] def captureIfNeeded(trace: Trace): Unit = captureLocal() match { case Some(c) => c.trace = trace - case None => // do nothing + case None => // do nothing } } diff --git a/core/src/main/scala/io/finch/ValidationRule.scala b/core/src/main/scala/io/finch/ValidationRule.scala index d9839b6a4..75dab04e1 100644 --- a/core/src/main/scala/io/finch/ValidationRule.scala +++ b/core/src/main/scala/io/finch/ValidationRule.scala @@ -36,7 +36,7 @@ trait ValidationRule[A] { self => * @return a new rule that only validates if both the combined rules validate */ def and(that: ValidationRule[A]): ValidationRule[A] = - ValidationRule(s"${self.description} and ${that.description}") { value => self(value) && that(value) } + ValidationRule(s"${self.description} and ${that.description}")(value => self(value) && that(value)) /** * Combines this rule with another rule such that the new rule validates if any one of the @@ -47,40 +47,40 @@ trait ValidationRule[A] { self => * @return a new rule that validates if any of the combined rules validates */ def or(that: ValidationRule[A]): ValidationRule[A] = - ValidationRule(s"${self.description} or ${that.description}") { - value => self(value) || that(value) + ValidationRule(s"${self.description} or ${that.description}") { value => + self(value) || that(value) } } /** - * Allows the creation of reusable validation rules for [[Endpoint]]s. - */ + * Allows the creation of reusable validation rules for [[Endpoint]]s. + */ object ValidationRule { + /** - * Implicit conversion that allows the same [[ValidationRule]] to be used for required - * and optional values. If the optional value is non-empty, it gets validated (and validation may - * fail, producing an error), but if it is empty, it is always treated as valid. - * - * @param rule the validation rule to adapt for optional values - * - * @return a new validation rule that applies the specified rule to an optional value in case it - * is not empty - */ - implicit def toOptionalRule[A](rule: ValidationRule[A]): ValidationRule[Option[A]] = { + * Implicit conversion that allows the same [[ValidationRule]] to be used for required + * and optional values. If the optional value is non-empty, it gets validated (and validation may + * fail, producing an error), but if it is empty, it is always treated as valid. + * + * @param rule the validation rule to adapt for optional values + * + * @return a new validation rule that applies the specified rule to an optional value in case it + * is not empty + */ + implicit def toOptionalRule[A](rule: ValidationRule[A]): ValidationRule[Option[A]] = ValidationRule(rule.description) { case Some(value) => rule(value) - case None => true + case None => true } - } /** - * Creates a new reusable [[ValidationRule]] based on the specified predicate. - * - * @param desc text describing the rule being validated - * @param p returns true if the data is valid - * - * @return a new reusable validation rule. - */ + * Creates a new reusable [[ValidationRule]] based on the specified predicate. + * + * @param desc text describing the rule being validated + * @param p returns true if the data is valid + * + * @return a new reusable validation rule. + */ def apply[A](desc: String)(p: A => Boolean): ValidationRule[A] = new ValidationRule[A] { def description: String = desc def apply(value: A): Boolean = p(value) diff --git a/core/src/main/scala/io/finch/ValidationRules.scala b/core/src/main/scala/io/finch/ValidationRules.scala index e97900a66..4f6f23aa6 100644 --- a/core/src/main/scala/io/finch/ValidationRules.scala +++ b/core/src/main/scala/io/finch/ValidationRules.scala @@ -1,27 +1,28 @@ package io.finch trait ValidationRules { + /** - * A [[ValidationRule]] that makes sure the numeric value is greater than given `n`. - */ + * A [[ValidationRule]] that makes sure the numeric value is greater than given `n`. + */ def beGreaterThan[A](n: A)(implicit ev: Numeric[A]): ValidationRule[A] = ValidationRule(s"be greater than $n")(ev.gt(_, n)) /** - * A [[ValidationRule]] that makes sure the numeric value is less than given `n`. - */ + * A [[ValidationRule]] that makes sure the numeric value is less than given `n`. + */ def beLessThan[A](n: A)(implicit ev: Numeric[A]): ValidationRule[A] = ValidationRule(s"be less than $n")(ev.lt(_, n)) /** - * A [[ValidationRule]] that makes sure the string value is longer than `n` symbols. - */ + * A [[ValidationRule]] that makes sure the string value is longer than `n` symbols. + */ def beLongerThan(n: Int): ValidationRule[String] = ValidationRule(s"be longer than $n symbols")(_.length > n) /** - * A [[ValidationRule]] that makes sure the string value is shorter than `n` symbols. - */ + * A [[ValidationRule]] that makes sure the string value is shorter than `n` symbols. + */ def beShorterThan(n: Int): ValidationRule[String] = ValidationRule(s"be shorter than $n symbols")(_.length < n) } diff --git a/core/src/main/scala/io/finch/endpoint/body.scala b/core/src/main/scala/io/finch/endpoint/body.scala index 467bc7f0b..e3fe5545e 100644 --- a/core/src/main/scala/io/finch/endpoint/body.scala +++ b/core/src/main/scala/io/finch/endpoint/body.scala @@ -1,14 +1,16 @@ package io.finch.endpoint +import java.nio.charset.{Charset, StandardCharsets} + +import scala.reflect.ClassTag + import cats.effect.Sync import com.twitter.io.{Buf, Reader} import io.finch._ import io.finch.internal._ import io.finch.items._ -import java.nio.charset.{Charset, StandardCharsets} -import scala.reflect.ClassTag -private[finch] abstract class FullBody[F[_], A] extends Endpoint[F, A] { +abstract private[finch] class FullBody[F[_], A] extends Endpoint[F, A] { protected def F: Sync[F] protected def missing: F[Output[A]] @@ -20,11 +22,12 @@ private[finch] abstract class FullBody[F[_], A] extends Endpoint[F, A] { val output = F.suspend { val contentLength = input.request.contentLengthOrNull if (contentLength == null || contentLength == "0") missing - else present( - input.request.mediaTypeOrEmpty, - input.request.content, - input.request.charsetOrUtf8 - ) + else + present( + input.request.mediaTypeOrEmpty, + input.request.content, + input.request.charsetOrUtf8 + ) } EndpointResult.Matched(input, Trace.empty, output) @@ -50,23 +53,23 @@ private[finch] object FullBody { } } -private[finch] abstract class Body[F[_], A, B, CT](implicit - dd: Decode.Dispatchable[A, CT], - ct: ClassTag[A], - protected val F: Sync[F] -) extends FullBody[F, B] with FullBody.PreparedBody[F, A, B] { +abstract private[finch] class Body[F[_], A, B, CT](implicit + dd: Decode.Dispatchable[A, CT], + ct: ClassTag[A], + protected val F: Sync[F] +) extends FullBody[F, B] + with FullBody.PreparedBody[F, A, B] { protected def present(contentType: String, content: Buf, cs: Charset): F[Output[B]] = dd(contentType, content, cs) match { case Right(s) => F.pure(Output.payload(prepare(s))) - case Left(e) => F.raiseError(Error.NotParsed(items.BodyItem, ct, e)) + case Left(e) => F.raiseError(Error.NotParsed(items.BodyItem, ct, e)) } final override def toString: String = "body" } -private[finch] abstract class BinaryBody[F[_], A](implicit protected val F: Sync[F]) - extends FullBody[F, A] with FullBody.PreparedBody[F, Array[Byte], A] { +abstract private[finch] class BinaryBody[F[_], A](implicit protected val F: Sync[F]) extends FullBody[F, A] with FullBody.PreparedBody[F, Array[Byte], A] { protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]] = F.pure(Output.payload(prepare(content.asByteArray))) @@ -74,9 +77,7 @@ private[finch] abstract class BinaryBody[F[_], A](implicit protected val F: Sync final override def toString: String = "binaryBody" } -private[finch] abstract class StringBody[F[_], A](implicit protected val F: Sync[F]) - extends FullBody[F, A] - with FullBody.PreparedBody[F, String, A] { +abstract private[finch] class StringBody[F[_], A](implicit protected val F: Sync[F]) extends FullBody[F, A] with FullBody.PreparedBody[F, String, A] { protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]] = F.pure(Output.payload(prepare(content.asString(cs)))) @@ -84,26 +85,28 @@ private[finch] abstract class StringBody[F[_], A](implicit protected val F: Sync final override def toString: String = "stringBody" } -private[finch] abstract class ChunkedBody[F[_], S[_[_], _], A] extends Endpoint[F, S[F, A]] { +abstract private[finch] class ChunkedBody[F[_], S[_[_], _], A] extends Endpoint[F, S[F, A]] { protected def F: Sync[F] protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, A]] final def apply(input: Input): EndpointResult[F, S[F, A]] = if (!input.request.isChunked) EndpointResult.NotMatched[F] - else EndpointResult.Matched( - input, - Trace.empty, - F.delay(prepare(input.request.reader, input.request.charsetOrUtf8)) - ) + else + EndpointResult.Matched( + input, + Trace.empty, + F.delay(prepare(input.request.reader, input.request.charsetOrUtf8)) + ) final override def item: RequestItem = items.BodyItem } -private[finch] final class BinaryBodyStream[F[_], S[_[_], _]](implicit - LR: LiftReader[S, F], - protected val F: Sync[F] -) extends ChunkedBody[F, S, Array[Byte]] with (Buf => Array[Byte]) { +final private[finch] class BinaryBodyStream[F[_], S[_[_], _]](implicit + LR: LiftReader[S, F], + protected val F: Sync[F] +) extends ChunkedBody[F, S, Array[Byte]] + with (Buf => Array[Byte]) { def apply(buf: Buf): Array[Byte] = buf.asByteArray @@ -113,25 +116,26 @@ private[finch] final class BinaryBodyStream[F[_], S[_[_], _]](implicit override def toString: String = "binaryBodyStream" } -private[finch] final class StringBodyStream[F[_], S[_[_], _]](implicit - LR: LiftReader[S, F], - protected val F: Sync[F] -) extends ChunkedBody[F, S, String] with (Buf => String) { +final private[finch] class StringBodyStream[F[_], S[_[_], _]](implicit + LR: LiftReader[S, F], + protected val F: Sync[F] +) extends ChunkedBody[F, S, String] + with (Buf => String) { def apply(buf: Buf): String = buf.asString(StandardCharsets.UTF_8) protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, String]] = cs match { case StandardCharsets.UTF_8 => Output.payload(LR(r, this)) - case _ => Output.payload(LR(r, _.asString(cs))) + case _ => Output.payload(LR(r, _.asString(cs))) } override def toString: String = "stringBodyStream" } -private[finch] final class BodyStream[F[_], S[_[_], _], A, CT <: String](implicit - protected val F: Sync[F], - LR: LiftReader[S, F], - A: DecodeStream.Aux[S, F, A, CT] +final private[finch] class BodyStream[F[_], S[_[_], _], A, CT <: String](implicit + protected val F: Sync[F], + LR: LiftReader[S, F], + A: DecodeStream.Aux[S, F, A, CT] ) extends ChunkedBody[F, S, A] { protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, A]] = diff --git a/core/src/main/scala/io/finch/endpoint/cookie.scala b/core/src/main/scala/io/finch/endpoint/cookie.scala index 7be52cb3d..a002c38d0 100644 --- a/core/src/main/scala/io/finch/endpoint/cookie.scala +++ b/core/src/main/scala/io/finch/endpoint/cookie.scala @@ -4,8 +4,8 @@ import cats.effect.Sync import com.twitter.finagle.http.{Cookie => FinagleCookie} import io.finch._ -private[finch] abstract class Cookie[F[_], A](name: String)(implicit - protected val F: Sync[F] +abstract private[finch] class Cookie[F[_], A](name: String)(implicit + protected val F: Sync[F] ) extends Endpoint[F, A] { protected def missing(name: String): F[Output[A]] @@ -14,7 +14,7 @@ private[finch] abstract class Cookie[F[_], A](name: String)(implicit def apply(input: Input): EndpointResult[F, A] = { val output = F.suspend { input.request.cookies.get(name) match { - case None => missing(name) + case None => missing(name) case Some(value) => present(value) } } diff --git a/core/src/main/scala/io/finch/endpoint/endpoint.scala b/core/src/main/scala/io/finch/endpoint/endpoint.scala index 09e5c756f..ad16838c9 100644 --- a/core/src/main/scala/io/finch/endpoint/endpoint.scala +++ b/core/src/main/scala/io/finch/endpoint/endpoint.scala @@ -1,17 +1,19 @@ package io.finch +import java.io.InputStream + import cats.Applicative import cats.effect.{ContextShift, Resource, Sync} import cats.syntax.all._ import com.twitter.finagle.http.{Method => FinagleMethod} import com.twitter.io.Buf -import java.io.InputStream import shapeless.HNil package object endpoint { - private[finch] class FromInputStream[F[_]](stream: Resource[F, InputStream])( - implicit F: Sync[F], S: ContextShift[F] + private[finch] class FromInputStream[F[_]](stream: Resource[F, InputStream])(implicit + F: Sync[F], + S: ContextShift[F] ) extends Endpoint[F, Buf] { private def readLoop(left: Buf, stream: InputStream): F[Buf] = F.suspend { @@ -25,9 +27,7 @@ package object endpoint { EndpointResult.Matched( input, Trace.empty, - stream.use(s => - S.shift.flatMap(_ => readLoop(Buf.Empty, s)).map(buf => Output.payload(buf)) - ) + stream.use(s => S.shift.flatMap(_ => readLoop(Buf.Empty, s)).map(buf => Output.payload(buf))) ) } diff --git a/core/src/main/scala/io/finch/endpoint/header.scala b/core/src/main/scala/io/finch/endpoint/header.scala index f0993ec51..7050fd805 100644 --- a/core/src/main/scala/io/finch/endpoint/header.scala +++ b/core/src/main/scala/io/finch/endpoint/header.scala @@ -1,15 +1,16 @@ package io.finch.endpoint +import scala.reflect.ClassTag + import cats.Id import cats.effect.Sync import io.finch._ import io.finch.items._ -import scala.reflect.ClassTag -private[finch] abstract class Header[F[_], G[_], A](name: String)(implicit - d: DecodeEntity[A], - tag: ClassTag[A], - protected val F: Sync[F] +abstract private[finch] class Header[F[_], G[_], A](name: String)(implicit + d: DecodeEntity[A], + tag: ClassTag[A], + protected val F: Sync[F] ) extends Endpoint[F, G[A]] { self => protected def missing(name: String): F[Output[G[A]]] @@ -19,10 +20,11 @@ private[finch] abstract class Header[F[_], G[_], A](name: String)(implicit val output: F[Output[G[A]]] = F.suspend { input.request.headerMap.getOrNull(name) match { case null => missing(name) - case value => d(value) match { - case Right(s) => F.pure(Output.payload(present(s))) - case Left(e) => F.raiseError(Error.NotParsed(items.HeaderItem(name), tag, e)) - } + case value => + d(value) match { + case Right(s) => F.pure(Output.payload(present(s))) + case Left(e) => F.raiseError(Error.NotParsed(items.HeaderItem(name), tag, e)) + } } } @@ -35,7 +37,7 @@ private[finch] abstract class Header[F[_], G[_], A](name: String)(implicit private[finch] object Header { - trait Required[F[_], A] {_: Header[F, Id, A] => + trait Required[F[_], A] { _: Header[F, Id, A] => protected def missing(name: String): F[Output[A]] = F.raiseError(Error.NotPresent(items.HeaderItem(name))) protected def present(value: A): Id[A] = value diff --git a/core/src/main/scala/io/finch/endpoint/method.scala b/core/src/main/scala/io/finch/endpoint/method.scala index 5fbc8ffad..9c779fc19 100644 --- a/core/src/main/scala/io/finch/endpoint/method.scala +++ b/core/src/main/scala/io/finch/endpoint/method.scala @@ -7,10 +7,11 @@ private[finch] class Method[F[_], A](m: FinagleMethod, e: Endpoint[F, A]) extend final def apply(input: Input): EndpointResult[F, A] = if (input.request.method == m) e(input) - else e(input) match { - case EndpointResult.Matched(_, _, _) => EndpointResult.NotMatched.MethodNotAllowed(m :: Nil) - case skipped => skipped - } + else + e(input) match { + case EndpointResult.Matched(_, _, _) => EndpointResult.NotMatched.MethodNotAllowed(m :: Nil) + case skipped => skipped + } - final override def toString: String = s"${ m.toString.toUpperCase } /${ e.toString }" + final override def toString: String = s"${m.toString.toUpperCase} /${e.toString}" } diff --git a/core/src/main/scala/io/finch/endpoint/multipart.scala b/core/src/main/scala/io/finch/endpoint/multipart.scala index 3c12205a4..a1cbfb1e2 100644 --- a/core/src/main/scala/io/finch/endpoint/multipart.scala +++ b/core/src/main/scala/io/finch/endpoint/multipart.scala @@ -1,19 +1,20 @@ package io.finch.endpoint +import scala.reflect.ClassTag +import scala.util.control.NonFatal + import cats.Id import cats.data.NonEmptyList import cats.effect.Sync import com.twitter.finagle.http.Request -import com.twitter.finagle.http.exp.{Multipart => FinagleMultipart, MultipartDecoder} import com.twitter.finagle.http.exp.Multipart.{FileUpload => FinagleFileUpload} +import com.twitter.finagle.http.exp.{Multipart => FinagleMultipart, MultipartDecoder} import io.finch._ import io.finch.items._ -import scala.reflect.ClassTag -import scala.util.control.NonFatal -private[finch] abstract class Attribute[F[_]: Sync, G[_], A](val name: String)(implicit - d: DecodeEntity[A], - tag: ClassTag[A] +abstract private[finch] class Attribute[F[_]: Sync, G[_], A](val name: String)(implicit + d: DecodeEntity[A], + tag: ClassTag[A] ) extends Endpoint[F, G[A]] { protected def F: Sync[F] = Sync[F] @@ -21,15 +22,14 @@ private[finch] abstract class Attribute[F[_]: Sync, G[_], A](val name: String)(i protected def present(value: NonEmptyList[A]): F[Output[G[A]]] protected def unparsed(errors: NonEmptyList[Throwable], tag: ClassTag[A]): F[Output[G[A]]] - private def all(input: Input): Option[NonEmptyList[String]] = { + private def all(input: Input): Option[NonEmptyList[String]] = for { m <- Multipart.decodeIfNeeded(input.request) attrs <- m.attributes.get(name) nel <- NonEmptyList.fromList(attrs.toList) } yield nel - } - final def apply(input: Input): EndpointResult[F, G[A]] = { + final def apply(input: Input): EndpointResult[F, G[A]] = if (input.request.isChunked) EndpointResult.NotMatched[F] else { val output = F.suspend { @@ -40,7 +40,7 @@ private[finch] abstract class Attribute[F[_]: Sync, G[_], A](val name: String)(i val errors = decoded.collect { case Left(t) => t } NonEmptyList.fromList(errors) match { - case None => present(decoded.map(_.right.get)) + case None => present(decoded.map(_.right.get)) case Some(es) => unparsed(es, tag) } } @@ -48,7 +48,6 @@ private[finch] abstract class Attribute[F[_]: Sync, G[_], A](val name: String)(i EndpointResult.Matched(input, Trace.empty, output) } - } final override def item: items.RequestItem = items.ParamItem(name) } @@ -98,14 +97,13 @@ private[finch] object Attribute { } } -private[finch] abstract class FileUpload[F[_]: Sync, G[_]](name: String) - extends Endpoint[F, G[FinagleMultipart.FileUpload]] { +abstract private[finch] class FileUpload[F[_]: Sync, G[_]](name: String) extends Endpoint[F, G[FinagleMultipart.FileUpload]] { protected def F: Sync[F] = Sync[F] protected def missing(name: String): F[Output[G[FinagleFileUpload]]] protected def present(a: NonEmptyList[FinagleFileUpload]): F[Output[G[FinagleFileUpload]]] - private final def all(input: Input): Option[NonEmptyList[FinagleFileUpload]] = + final private def all(input: Input): Option[NonEmptyList[FinagleFileUpload]] = for { mp <- Multipart.decodeIfNeeded(input.request) all <- mp.files.get(name) @@ -118,7 +116,7 @@ private[finch] abstract class FileUpload[F[_]: Sync, G[_]](name: String) val output = Sync[F].suspend { all(input) match { case Some(nel) => present(nel) - case None => missing(name) + case None => missing(name) } } @@ -129,7 +127,6 @@ private[finch] abstract class FileUpload[F[_]: Sync, G[_]](name: String) final override def toString: String = s"fileUpload($name)" } - private[finch] object FileUpload { trait Required[F[_]] { _: FileUpload[F, Id] => @@ -165,7 +162,8 @@ private[finch] object Multipart { private val field = Request.Schema.newField[Option[FinagleMultipart]](null) def decodeNow(req: Request): Option[FinagleMultipart] = - try MultipartDecoder.decode(req) catch { case NonFatal(_) => None } + try MultipartDecoder.decode(req) + catch { case NonFatal(_) => None } def decodeIfNeeded(req: Request): Option[FinagleMultipart] = req.ctx(field) match { case null => // was never decoded for this request diff --git a/core/src/main/scala/io/finch/endpoint/param.scala b/core/src/main/scala/io/finch/endpoint/param.scala index 362bedc03..2de558f78 100644 --- a/core/src/main/scala/io/finch/endpoint/param.scala +++ b/core/src/main/scala/io/finch/endpoint/param.scala @@ -1,15 +1,16 @@ package io.finch.endpoint +import scala.reflect.ClassTag + import cats.Id import cats.data.NonEmptyList import cats.effect.Sync import io.finch._ -import scala.reflect.ClassTag -private[finch] abstract class Param[F[_], G[_], A](name: String)(implicit - d: DecodeEntity[A], - tag: ClassTag[A], - protected val F: Sync[F] +abstract private[finch] class Param[F[_], G[_], A](name: String)(implicit + d: DecodeEntity[A], + tag: ClassTag[A], + protected val F: Sync[F] ) extends Endpoint[F, G[A]] { self => protected def missing(name: String): F[Output[G[A]]] @@ -19,10 +20,11 @@ private[finch] abstract class Param[F[_], G[_], A](name: String)(implicit val output: F[Output[G[A]]] = F.suspend { input.request.params.get(name) match { case None => missing(name) - case Some(value) => d(value) match { - case Right(s) => F.pure(Output.payload(present(s))) - case Left(e) => F.raiseError(Error.NotParsed(items.ParamItem(name), tag, e)) - } + case Some(value) => + d(value) match { + case Right(s) => F.pure(Output.payload(present(s))) + case Left(e) => F.raiseError(Error.NotParsed(items.ParamItem(name), tag, e)) + } } } @@ -47,10 +49,10 @@ private[finch] object Param { } } -private[finch] abstract class Params[F[_], G[_], A](name: String)(implicit - d: DecodeEntity[A], - tag: ClassTag[A], - protected val F: Sync[F] +abstract private[finch] class Params[F[_], G[_], A](name: String)(implicit + d: DecodeEntity[A], + tag: ClassTag[A], + protected val F: Sync[F] ) extends Endpoint[F, G[A]] { protected def missing(name: String): F[Output[G[A]]] diff --git a/core/src/main/scala/io/finch/endpoint/path.scala b/core/src/main/scala/io/finch/endpoint/path.scala index cb04b3eb1..cb31e46bc 100644 --- a/core/src/main/scala/io/finch/endpoint/path.scala +++ b/core/src/main/scala/io/finch/endpoint/path.scala @@ -1,13 +1,14 @@ package io.finch.endpoint +import scala.reflect.ClassTag + import cats.Applicative import io.finch._ import io.netty.handler.codec.http.QueryStringDecoder -import scala.reflect.ClassTag import shapeless.HNil private[finch] class MatchPath[F[_]](s: String)(implicit - F: Applicative[F] + F: Applicative[F] ) extends Endpoint[F, HNil] { final def apply(input: Input): EndpointResult[F, HNil] = input.route match { case `s` :: rest => @@ -23,20 +24,21 @@ private[finch] class MatchPath[F[_]](s: String)(implicit } private[finch] class ExtractPath[F[_], A](implicit - d: DecodePath[A], - ct: ClassTag[A], - F: Applicative[F] + d: DecodePath[A], + ct: ClassTag[A], + F: Applicative[F] ) extends Endpoint[F, A] { final def apply(input: Input): EndpointResult[F, A] = input.route match { - case s :: rest => d(QueryStringDecoder.decodeComponent(s)) match { - case Some(a) => - EndpointResult.Matched( - input.withRoute(rest), - Trace.segment(toString), - F.pure(Output.payload(a)) - ) - case _ => EndpointResult.NotMatched[F] - } + case s :: rest => + d(QueryStringDecoder.decodeComponent(s)) match { + case Some(a) => + EndpointResult.Matched( + input.withRoute(rest), + Trace.segment(toString), + F.pure(Output.payload(a)) + ) + case _ => EndpointResult.NotMatched[F] + } case _ => EndpointResult.NotMatched[F] } @@ -44,9 +46,9 @@ private[finch] class ExtractPath[F[_], A](implicit } private[finch] class ExtractPaths[F[_], A](implicit - d: DecodePath[A], - ct: ClassTag[A], - F: Applicative[F] + d: DecodePath[A], + ct: ClassTag[A], + F: Applicative[F] ) extends Endpoint[F, List[A]] { final def apply(input: Input): EndpointResult[F, List[A]] = EndpointResult.Matched( input.copy(route = Nil), diff --git a/core/src/main/scala/io/finch/internal/Mapper.scala b/core/src/main/scala/io/finch/internal/Mapper.scala index 6f43f1c00..821be9940 100644 --- a/core/src/main/scala/io/finch/internal/Mapper.scala +++ b/core/src/main/scala/io/finch/internal/Mapper.scala @@ -9,21 +9,20 @@ import shapeless.HNil import shapeless.ops.function.FnToProduct /** - * A type class that allows the [[Endpoint]] to be mapped to either `A => B` or `A => Future[B]`. - * @groupname LowPriorityMapper Low Priority Mapper Conversions - * @groupprio LowPriorityMapper 0 - * @groupname HighPriorityMapper High priority mapper conversions - * @groupprio HighPriorityMapper 1 - */ + * A type class that allows the [[Endpoint]] to be mapped to either `A => B` or `A => Future[B]`. + * @groupname LowPriorityMapper Low Priority Mapper Conversions + * @groupprio LowPriorityMapper 0 + * @groupname HighPriorityMapper High priority mapper conversions + * @groupprio HighPriorityMapper 1 + */ trait Mapper[F[_], A] { type Out /** - * - * @param e The endpoint to map - * @tparam X Hack to stop the compiler from converting this to a SAM - * @return An endpoint that returns an `Out` - */ + * @param e The endpoint to map + * @tparam X Hack to stop the compiler from converting this to a SAM + * @return An endpoint that returns an `Out` + */ def apply[X](e: Endpoint[F, A]): Endpoint[F, Out] } @@ -40,83 +39,81 @@ private[finch] trait LowPriorityMapperConversions { * @group LowPriorityMapper */ implicit def mapperFromOutputFunction[F[_], A, B](f: A => Output[B])(implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): Mapper.Aux[F, A, B] = instance(_.mapOutput(f)) /** - * @group LowPriorityMapper - */ + * @group LowPriorityMapper + */ implicit def mapperFromResponseFunction[F[_], A](f: A => Response)(implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): Mapper.Aux[F, A, Response] = instance(_.mapOutput(f.andThen(r => Output.payload(r, r.status)))) } private[finch] trait HighPriorityMapperConversions extends LowPriorityMapperConversions { + /** * @group HighPriorityMapper */ implicit def mapperFromOutputHFunction[F[_], A, B, FN, OB](f: FN)(implicit - F: MonadError[F, Throwable], - ftp: FnToProduct.Aux[FN, A => OB], - ev: OB <:< Output[B] + F: MonadError[F, Throwable], + ftp: FnToProduct.Aux[FN, A => OB], + ev: OB <:< Output[B] ): Mapper.Aux[F, A, B] = instance(_.mapOutput(value => ev(ftp(f)(value)))) - /** - * @group HighPriorityMapper - */ + * @group HighPriorityMapper + */ implicit def mapperFromResponseHFunction[F[_], A, FN, R](f: FN)(implicit - F: MonadError[F, Throwable], - ftp: FnToProduct.Aux[FN, A => R], - ev: R <:< Response + F: MonadError[F, Throwable], + ftp: FnToProduct.Aux[FN, A => R], + ev: R <:< Response ): Mapper.Aux[F, A, Response] = instance(_.mapOutput { value => val r = ev(ftp(f)(value)) Output.payload(r, r.status) }) /** - * @group HighPriorityMapper - */ + * @group HighPriorityMapper + */ implicit def mapperFromOutputValue[F[_], A](o: => Output[A])(implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): Mapper.Aux[F, HNil, A] = instance(_.mapOutput(_ => o)) /** - * @group HighPriorityMapper - */ + * @group HighPriorityMapper + */ implicit def mapperFromResponseValue[F[_]](r: => Response)(implicit - F: MonadError[F, Throwable] + F: MonadError[F, Throwable] ): Mapper.Aux[F, HNil, Response] = instance(_.mapOutput(_ => Output.payload(r, r.status))) - implicit def mapperFromKindToEffectOutputFunction[A, B, F[_], G[_]: Async](f: A => F[Output[B]])( - implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, B] = + implicit def mapperFromKindToEffectOutputFunction[A, B, F[_], G[_]: Async](f: A => F[Output[B]])(implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, B] = instance(_.mapOutputAsync(a => conv.apply(f(a)))) - implicit def mapperFromKindToEffectOutputValue[A, B, F[_], G[_]: Async](f: => F[Output[B]])( - implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, B] = instance(_.mapOutputAsync(_ => conv.apply(f))) + implicit def mapperFromKindToEffectOutputValue[A, B, F[_], G[_]: Async](f: => F[Output[B]])(implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, B] = instance( + _.mapOutputAsync(_ => conv.apply(f)) + ) - implicit def mapperFromKindToEffectResponsFunction[A, F[_], G[_]: Async](f: A => F[Response])( - implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, Response] = + implicit def mapperFromKindToEffectResponsFunction[A, F[_], G[_]: Async](f: A => F[Response])(implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, Response] = instance(_.mapOutputAsync(f.andThen(fr => conv(fr).map(r => Output.payload(r, r.status))))) - implicit def mapperFromKindToEffectResponseValue[A, F[_], G[_]: Async](f: => F[Response])( - implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, Response] = - instance(_.mapOutputAsync(_=>conv(f).map(r => Output.payload(r, r.status)))) + implicit def mapperFromKindToEffectResponseValue[A, F[_], G[_]: Async](f: => F[Response])(implicit conv: ToAsync[F, G]): Mapper.Aux[G, A, Response] = + instance(_.mapOutputAsync(_ => conv(f).map(r => Output.payload(r, r.status)))) } object Mapper extends HighPriorityMapperConversions { implicit def mapperFromKindOutputHFunction[F[_]: Async, G[_], A, B, FN, FOB](f: FN)(implicit - ftp: FnToProduct.Aux[FN, A => FOB], - ev: FOB <:< G[Output[B]], - conv: ToAsync[G, F] + ftp: FnToProduct.Aux[FN, A => FOB], + ev: FOB <:< G[Output[B]], + conv: ToAsync[G, F] ): Mapper.Aux[F, A, B] = instance(_.mapOutputAsync(a => conv.apply(ev(ftp(f)(a))))) - implicit def mapperFromKindResponseHFunction[F[_] : Async, G[_], A, FN, FR](f: FN)(implicit - ftp: FnToProduct.Aux[FN, A => FR], - ev: FR <:< G[Response], - conv: ToAsync[G, F] + implicit def mapperFromKindResponseHFunction[F[_]: Async, G[_], A, FN, FR](f: FN)(implicit + ftp: FnToProduct.Aux[FN, A => FR], + ev: FR <:< G[Response], + conv: ToAsync[G, F] ): Mapper.Aux[F, A, Response] = instance(_.mapOutputAsync { value => val fr = conv(ev(ftp(f)(value))) fr.map(r => Output.payload(r, r.status)) diff --git a/core/src/main/scala/io/finch/internal/PairJoin.scala b/core/src/main/scala/io/finch/internal/PairJoin.scala index e2a6b1510..5ad9fd131 100644 --- a/core/src/main/scala/io/finch/internal/PairJoin.scala +++ b/core/src/main/scala/io/finch/internal/PairJoin.scala @@ -1,24 +1,24 @@ package io.finch.internal -import shapeless.{::, DepFn2, HNil} import shapeless.ops.adjoin.Adjoin +import shapeless.{::, DepFn2, HNil} /** - * We need a version of [[shapeless.ops.adjoin.Adjoin]] that provides slightly different behavior in - * the case of singleton results (we simply return the value, not a singleton `HList`). - * @groupname LowPriorityPair Low priority `PairAdjoin` - * @groupprio LowPriorityPair 0 - */ + * We need a version of [[shapeless.ops.adjoin.Adjoin]] that provides slightly different behavior in + * the case of singleton results (we simply return the value, not a singleton `HList`). + * @groupname LowPriorityPair Low priority `PairAdjoin` + * @groupprio LowPriorityPair 0 + */ trait PairAdjoin[A, B] extends DepFn2[A, B] private[finch] trait LowPriorityPairAdjoin { type Aux[A, B, Out0] = PairAdjoin[A, B] { type Out = Out0 } /** - * @group LowPriorityPair - */ + * @group LowPriorityPair + */ implicit def pairAdjoin[A, B, Out0](implicit - adjoin: Adjoin.Aux[A :: B :: HNil, Out0] + adjoin: Adjoin.Aux[A :: B :: HNil, Out0] ): Aux[A, B, Out0] = new PairAdjoin[A, B] { type Out = Out0 @@ -29,7 +29,7 @@ private[finch] trait LowPriorityPairAdjoin { object PairAdjoin extends LowPriorityPairAdjoin { implicit def singletonPairAdjoin[A, B, C](implicit - adjoin: Adjoin.Aux[A :: B :: HNil, C :: HNil] + adjoin: Adjoin.Aux[A :: B :: HNil, C :: HNil] ): Aux[A, B, C] = new PairAdjoin[A, B] { type Out = C diff --git a/core/src/main/scala/io/finch/internal/ToAsync.scala b/core/src/main/scala/io/finch/internal/ToAsync.scala index 2f1c68010..4cfedaa90 100644 --- a/core/src/main/scala/io/finch/internal/ToAsync.scala +++ b/core/src/main/scala/io/finch/internal/ToAsync.scala @@ -1,10 +1,11 @@ package io.finch.internal +import scala.concurrent.{Future => ScalaFuture} +import scala.util.{Failure, Success} + import cats.effect.Async import cats.~> import com.twitter.util.{Future => TwitterFuture, Return, Throw} -import scala.concurrent.{Future => ScalaFuture} -import scala.util.{Failure, Success} trait ToAsync[A[_], B[_]] extends ~>[A, B] @@ -19,7 +20,7 @@ object ToAsync { Async[E].async { cb => a.respond { case Return(r) => cb(Right(r)) - case Throw(t) => cb(Left(t)) + case Throw(t) => cb(Left(t)) } } } diff --git a/core/src/main/scala/io/finch/internal/currentTime.scala b/core/src/main/scala/io/finch/internal/currentTime.scala index 4b4657076..6e7087a4a 100644 --- a/core/src/main/scala/io/finch/internal/currentTime.scala +++ b/core/src/main/scala/io/finch/internal/currentTime.scala @@ -1,15 +1,12 @@ package io.finch.internal -import java.time.{Instant, ZoneId} import java.time.format.DateTimeFormatter +import java.time.{Instant, ZoneId} import java.util.Locale - object currentTime { private val formatter: DateTimeFormatter = - DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz") - .withLocale(Locale.ENGLISH) - .withZone(ZoneId.of("GMT")) + DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz").withLocale(Locale.ENGLISH).withZone(ZoneId.of("GMT")) private class Last(var millis: Long, var header: String) diff --git a/core/src/main/scala/io/finch/internal/newLine.scala b/core/src/main/scala/io/finch/internal/newLine.scala index b78d89f42..131210d83 100644 --- a/core/src/main/scala/io/finch/internal/newLine.scala +++ b/core/src/main/scala/io/finch/internal/newLine.scala @@ -1,8 +1,9 @@ package io.finch.internal -import com.twitter.io.Buf import java.nio.charset.{Charset, StandardCharsets} +import com.twitter.io.Buf + object newLine { private def fromCharset(cs: Charset): Buf = Buf.ByteArray.Owned("\n".getBytes(cs)) @@ -14,13 +15,13 @@ object newLine { private val utf32 = fromCharset(Utf32) final def apply(cs: Charset): Buf = cs match { - case StandardCharsets.UTF_8 => ascii - case StandardCharsets.US_ASCII => ascii + case StandardCharsets.UTF_8 => ascii + case StandardCharsets.US_ASCII => ascii case StandardCharsets.ISO_8859_1 => ascii - case StandardCharsets.UTF_16 => utf16 - case StandardCharsets.UTF_16BE => utf16be - case StandardCharsets.UTF_16LE => utf16le - case Utf32 => utf32 - case _ => fromCharset(cs) + case StandardCharsets.UTF_16 => utf16 + case StandardCharsets.UTF_16BE => utf16be + case StandardCharsets.UTF_16LE => utf16le + case Utf32 => utf32 + case _ => fromCharset(cs) } } diff --git a/core/src/main/scala/io/finch/internal/package.scala b/core/src/main/scala/io/finch/internal/package.scala index 8d41a76d2..cdcc67300 100644 --- a/core/src/main/scala/io/finch/internal/package.scala +++ b/core/src/main/scala/io/finch/internal/package.scala @@ -1,50 +1,51 @@ package io.finch -import com.twitter.finagle.http.{Fields, Message} -import com.twitter.io.Buf import java.nio.ByteBuffer import java.nio.charset.{Charset, StandardCharsets} +import com.twitter.finagle.http.{Fields, Message} +import com.twitter.io.Buf + /** - * This package contains an internal-use only type-classes and utilities that power Finch's API. - * - * It's not recommended to use any of the internal API directly, since it might change without any - * deprecation cycles. - */ + * This package contains an internal-use only type-classes and utilities that power Finch's API. + * + * It's not recommended to use any of the internal API directly, since it might change without any + * deprecation cycles. + */ package object internal { - @inline private[this] final val someTrue: Option[Boolean] = Some(true) - @inline private[this] final val someFalse: Option[Boolean] = Some(false) + @inline final private[this] val someTrue: Option[Boolean] = Some(true) + @inline final private[this] val someFalse: Option[Boolean] = Some(false) // Missing in StandardCharsets. val Utf32: Charset = Charset.forName("UTF-32") /** - * Enriches any string with fast `tooX` conversions. - */ + * Enriches any string with fast `tooX` conversions. + */ implicit class TooFastString(val s: String) extends AnyVal { /** - * Converts this string to the optional boolean value. - */ + * Converts this string to the optional boolean value. + */ final def tooBoolean: Option[Boolean] = s match { - case "true" => someTrue + case "true" => someTrue case "false" => someFalse - case _ => None + case _ => None } /** - * Converts this string to the optional integer value. The maximum allowed length for a number - * string is 32. - */ + * Converts this string to the optional integer value. The maximum allowed length for a number + * string is 32. + */ final def tooInt: Option[Int] = if (s.length == 0 || s.length > 32) None else parseInt(s) /** - * Converts this string to the optional long value. The maximum allowed length for a number - * string is 32. - */ + * Converts this string to the optional long value. The maximum allowed length for a number + * string is 32. + */ final def tooLong: Option[Long] = if (s.length == 0 || s.length > 32) None else parseLong(s) diff --git a/core/src/main/scala/io/finch/package.scala b/core/src/main/scala/io/finch/package.scala index a768c80b8..83ad58f5e 100644 --- a/core/src/main/scala/io/finch/package.scala +++ b/core/src/main/scala/io/finch/package.scala @@ -3,8 +3,8 @@ package io import cats.effect.IO /** - * This is a root package of the Finch library, which provides an immutable layer of functions and - * types atop of Finagle for writing lightweight HTTP services. + * This is a root package of the Finch library, which provides an immutable layer of functions and + * types atop of Finagle for writing lightweight HTTP services. */ package object finch extends Outputs with ValidationRules { @@ -13,7 +13,7 @@ package object finch extends Outputs with ValidationRules { object catsEffect extends EndpointModule[IO] object items { - sealed abstract class RequestItem(val kind: String, val nameOption:Option[String] = None) { + sealed abstract class RequestItem(val kind: String, val nameOption: Option[String] = None) { val description = kind + nameOption.fold("")(" '" + _ + "'") } final case class ParamItem(name: String) extends RequestItem("param", Some(name)) diff --git a/core/src/test/scala/io/finch/BodySpec.scala b/core/src/test/scala/io/finch/BodySpec.scala index 02618d0fa..5cbf4a189 100644 --- a/core/src/test/scala/io/finch/BodySpec.scala +++ b/core/src/test/scala/io/finch/BodySpec.scala @@ -1,9 +1,10 @@ package io.finch +import java.nio.charset.Charset + import com.twitter.finagle.http.Request import com.twitter.io.Buf import io.finch.data.Foo -import java.nio.charset.Charset import shapeless.{:+:, CNil} class BodySpec extends FinchSpec { @@ -83,7 +84,7 @@ class BodySpec extends FinchSpec { 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() + val Some(Left(error)) = b(i).awaitOutput() error shouldBe a[Error.NotParsed] error.getCause shouldBe Decode.UnsupportedMediaTypeException diff --git a/core/src/test/scala/io/finch/BootstrapSpec.scala b/core/src/test/scala/io/finch/BootstrapSpec.scala index 45f268d06..f4a3d98ab 100644 --- a/core/src/test/scala/io/finch/BootstrapSpec.scala +++ b/core/src/test/scala/io/finch/BootstrapSpec.scala @@ -1,11 +1,12 @@ package io.finch +import java.time.format.DateTimeFormatter +import java.time.{ZoneOffset, ZonedDateTime} + import cats.effect.IO import com.twitter.finagle.http.{Method, Request, Response, Status} import io.finch.data.Foo import io.finch.internal.currentTime -import java.time.{ZonedDateTime, ZoneOffset} -import java.time.format.DateTimeFormatter import shapeless.HNil class BootstrapSpec extends FinchSpec { @@ -39,15 +40,11 @@ class BootstrapSpec extends FinchSpec { } it should "respond 405 if method not allowed" in { - val a = get("foo") { Ok("get foo") } - val b = put("foo") { Ok("put foo") } - val c = post("foo") { Ok("post foo") } + val a = get("foo")(Ok("get foo")) + val b = put("foo")(Ok("put foo")) + val c = post("foo")(Ok("post foo")) - val s = Bootstrap - .configure(enableMethodNotAllowed = true) - .serve[Text.Plain](a :+: b) - .serve[Text.Plain](c) - .compile + val s = Bootstrap.configure(enableMethodNotAllowed = true).serve[Text.Plain](a :+: b).serve[Text.Plain](c).compile val aa = Request(Method.Get, "/foo") val bb = Request(Method.Put, "/foo") @@ -71,9 +68,7 @@ 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 s = Bootstrap.configure(enableUnsupportedMediaType = true).serve[Text.Plain](b).compile val i = Input.post("/").withBody[Application.Csv](Foo("bar")) @@ -95,9 +90,7 @@ class BootstrapSpec extends FinchSpec { def parseDate(s: String): Long = ZonedDateTime.parse(s, formatter).toEpochSecond check { (req: Request, include: Boolean) => - val s = Bootstrap.configure(includeDateHeader = include) - .serve[Text.Plain](Endpoint[IO].const(())) - .compile + val s = Bootstrap.configure(includeDateHeader = include).serve[Text.Plain](Endpoint[IO].const(())).compile val (_, Right(rep)) = s(req).unsafeRunSync() val now = parseDate(currentTime()) @@ -108,9 +101,7 @@ class BootstrapSpec extends FinchSpec { 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 s = Bootstrap.configure(includeServerHeader = include).serve[Text.Plain](Endpoint[IO].const(())).compile val (_, Right(rep)) = s(req).unsafeRunSync() @@ -122,9 +113,7 @@ class BootstrapSpec extends FinchSpec { check { req: Request => val p = req.path.split("/").drop(1) - val endpoint = p - .map(s => path(s)) - .foldLeft(Endpoint[IO].const(HNil : HNil))((p, e) => p :: e) + val endpoint = p.map(s => path(s)).foldLeft(Endpoint[IO].const(HNil: HNil))((p, e) => p :: e) val succ = endpoint.mapAsync(_ => IO.pure("foo")) val fail = endpoint.mapAsync(_ => IO.raiseError[String](new IllegalStateException)) diff --git a/core/src/test/scala/io/finch/DecodeEntityLaws.scala b/core/src/test/scala/io/finch/DecodeEntityLaws.scala index d3bab22f6..658bfa43b 100644 --- a/core/src/test/scala/io/finch/DecodeEntityLaws.scala +++ b/core/src/test/scala/io/finch/DecodeEntityLaws.scala @@ -17,7 +17,7 @@ trait DecodeEntityLaws[A] extends Laws with MissingInstances with AllInstances { def all(implicit A: Arbitrary[A], eq: Eq[A]): RuleSet = new DefaultRuleSet( name = "all", parent = None, - "roundTrip" -> Prop.forAll { (a: A) => roundTrip(a) } + "roundTrip" -> Prop.forAll((a: A) => roundTrip(a)) ) } diff --git a/core/src/test/scala/io/finch/DecodePathLaws.scala b/core/src/test/scala/io/finch/DecodePathLaws.scala index 0651ee623..70a96e4d2 100644 --- a/core/src/test/scala/io/finch/DecodePathLaws.scala +++ b/core/src/test/scala/io/finch/DecodePathLaws.scala @@ -17,7 +17,7 @@ trait DecodePathLaws[A] extends Laws with MissingInstances with AllInstances { def all(implicit A: Arbitrary[A], eq: Eq[A]): RuleSet = new DefaultRuleSet( name = "all", parent = None, - "roundTrip" -> Prop.forAll { (a: A) => roundTrip(a) } + "roundTrip" -> Prop.forAll((a: A) => roundTrip(a)) ) } diff --git a/core/src/test/scala/io/finch/EncodeLaws.scala b/core/src/test/scala/io/finch/EncodeLaws.scala index 41d0d6c18..27c649dba 100644 --- a/core/src/test/scala/io/finch/EncodeLaws.scala +++ b/core/src/test/scala/io/finch/EncodeLaws.scala @@ -20,7 +20,7 @@ trait EncodeLaws[A, CT <: String] extends Laws with MissingInstances with AllIns new DefaultRuleSet( name = "all", parent = None, - "roundTrip" -> Prop.forAll { (a: A, cs: Charset) => roundTrip(a, cs) } + "roundTrip" -> Prop.forAll((a: A, cs: Charset) => roundTrip(a, cs)) ) } diff --git a/core/src/test/scala/io/finch/EndToEndSpec.scala b/core/src/test/scala/io/finch/EndToEndSpec.scala index 156338aa7..e4349c12c 100644 --- a/core/src/test/scala/io/finch/EndToEndSpec.scala +++ b/core/src/test/scala/io/finch/EndToEndSpec.scala @@ -16,7 +16,7 @@ class EndToEndSpec extends FinchSpec { Application.Javascript :+: Application.OctetStream :+: Application.RssXml :+: Application.WwwFormUrlencoded :+: Application.Xml :+: Text.Plain :+: Text.Html :+: Text.EventStream :+: CNil - private implicit def encodeHNil[CT <: String]: Encode.Aux[HNil, CT] = Encode.instance((_, _) => Buf.Utf8("hnil")) + implicit private def encodeHNil[CT <: String]: Encode.Aux[HNil, CT] = Encode.instance((_, _) => Buf.Utf8("hnil")) private val allContentTypes = Seq( "application/json", @@ -38,9 +38,9 @@ class EndToEndSpec extends FinchSpec { val service: Service[Request, Response] = ( get("foo" :: path[String]) { s: String => Ok(Foo(s)) } :+: - get("bar") { Created("bar") } :+: - get("baz") { BadRequest(new IllegalArgumentException("foo")): Output[Unit] } :+: - get("qux" :: param[Foo]("foo")) { f: Foo => Created(f) } + get("bar")(Created("bar")) :+: + get("baz")(BadRequest(new IllegalArgumentException("foo")): Output[Unit]) :+: + get("qux" :: param[Foo]("foo")) { f: Foo => Created(f) } ).toServiceAs[Text.Plain] val rep1 = Await.result(service(Request("/foo/bar"))) @@ -61,7 +61,7 @@ class EndToEndSpec extends FinchSpec { } it should "convert value Endpoints into Services" in { - val e: Endpoint[IO, String] = get("foo") { Created("bar") } + val e: Endpoint[IO, String] = get("foo")(Created("bar")) val s: Service[Request, Response] = e.toServiceAs[Text.Plain] val rep = Await.result(s(Request("/foo"))) diff --git a/core/src/test/scala/io/finch/EndpointSpec.scala b/core/src/test/scala/io/finch/EndpointSpec.scala index cd09bba83..5017fee38 100644 --- a/core/src/test/scala/io/finch/EndpointSpec.scala +++ b/core/src/test/scala/io/finch/EndpointSpec.scala @@ -1,20 +1,21 @@ package io.finch +import java.io.{ByteArrayInputStream, InputStream} import java.util.UUID import java.util.concurrent.TimeUnit +import scala.concurrent.duration.Duration + import cats.data.{NonEmptyList, WriterT} import cats.effect.{IO, Resource} import cats.laws._ -import cats.laws.discipline._ import cats.laws.discipline.AlternativeTests import cats.laws.discipline.SemigroupalTests.Isomorphisms +import cats.laws.discipline._ import cats.~> import com.twitter.finagle.http.{Cookie, Method, Request} import com.twitter.io.Buf import io.finch.data.Foo -import java.io.{ByteArrayInputStream, InputStream} -import scala.concurrent.duration.Duration import shapeless._ class EndpointSpec extends FinchSpec { @@ -22,7 +23,7 @@ class EndpointSpec extends FinchSpec { type EndpointIO[A] = Endpoint[IO, A] implicit val isomorphisms: Isomorphisms[EndpointIO] = - Isomorphisms.invariant[EndpointIO](Endpoint.endpointInstances) + Isomorphisms.invariant[EndpointIO](Endpoint.endpointAlternative) checkAll("Endpoint[String]", AlternativeTests[EndpointIO].applicative[String, String, String]) @@ -73,9 +74,7 @@ class EndpointSpec extends FinchSpec { it should "propagate the output through mapOutputAsync and /" in { def expected(i: Int): Output[Int] = - Created(i) - .withHeader("A" -> "B") - .withCookie(new Cookie("C", "D")) + Created(i).withHeader("A" -> "B").withCookie(new Cookie("C", "D")) check { i: Input => path[String].mapOutputAsync(s => IO.pure(expected(s.length))).apply(i).awaitOutputUnsafe() === @@ -83,9 +82,7 @@ class EndpointSpec extends FinchSpec { } check { i: Input => - val e = i.route.dropRight(1) - .map(s => path(s)) - .foldLeft[Endpoint[IO, HNil]](zero)((acc, ee) => acc :: ee) + val e = i.route.dropRight(1).map(s => path(s)).foldLeft[Endpoint[IO, HNil]](zero)((acc, ee) => acc :: ee) val v = (e :: path[String]).mapOutputAsync(s => IO.pure(expected(s.length))).apply(i) v.awaitOutputUnsafe() === i.route.lastOption.map(s => expected(s.length)) @@ -94,10 +91,9 @@ class EndpointSpec extends FinchSpec { it should "match one patch segment" in { check { i: Input => - val v = i.route.headOption - .flatMap(s => path(s).apply(i).remainder) + val v = i.route.headOption.flatMap(s => path(s).apply(i).remainder) - v.isEmpty|| v === Some(i.withRoute(i.route.tail)) + v.isEmpty || v === Some(i.withRoute(i.route.tail)) } } @@ -115,10 +111,7 @@ class EndpointSpec extends FinchSpec { } it should "match the HTTP method" in { - def matchMethod( - m: Method, - f: Endpoint[IO, HNil] => Endpoint[IO, HNil]): Input => Boolean = { i: Input => - + def matchMethod(m: Method, f: Endpoint[IO, HNil] => Endpoint[IO, HNil]): Input => Boolean = { i: Input => val v = f(zero)(i) (i.request.method === m && v.remainder === Some(i)) || (i.request.method != m && v.remainder === None) @@ -175,7 +168,7 @@ class EndpointSpec extends FinchSpec { def methodMatcher( m: Method, f: Endpoint[IO, HNil] => Endpoint[IO, HNil] - ): String => Boolean = { s: String => f(s).toString === m.toString.toUpperCase + " /" + s } + ): String => Boolean = { s: String => f(s).toString === m.toString.toUpperCase + " /" + s } check(methodMatcher(Method.Get, get)) check(methodMatcher(Method.Post, post)) @@ -186,11 +179,11 @@ class EndpointSpec extends FinchSpec { check(methodMatcher(Method.Options, options)) check(methodMatcher(Method.Delete, delete)) - check { (s: String, i: Int) => path(s).map(_ => i).toString === s } - check { (s: String, t: String) => (path(s) :+: path(t)).toString === s"($s :+: $t)" } - check { (s: String, t: String) => (path(s) :: path(t)).toString === s"$s :: $t" } + check((s: String, i: Int) => path(s).map(_ => i).toString === s) + check((s: String, t: String) => (path(s) :+: path(t)).toString === s"($s :+: $t)") + check((s: String, t: String) => (path(s) :: path(t)).toString === s"$s :: $t") check { s: String => path(s).product[String](pathAny.map(_ => "foo")).toString === s } - check { (s: String, t: String) => path(s).mapAsync(_ => IO.pure(t)).toString === s } + check((s: String, t: String) => path(s).mapAsync(_ => IO.pure(t)).toString === s) pathEmpty.toString shouldBe "" pathAny.toString shouldBe "*" @@ -236,9 +229,12 @@ class EndpointSpec extends FinchSpec { it should "rescue the exception occurred in it" in { check { (i: Input, s: String, e: Exception) => - val result = liftAsync[String](IO.raiseError(e)).handle { - case _ => Created(s) - }.apply(i).awaitOutput() + val result = liftAsync[String](IO.raiseError(e)) + .handle { case _ => + Created(s) + } + .apply(i) + .awaitOutput() result === Some(Right(Created(s))) } } @@ -247,9 +243,12 @@ class EndpointSpec extends FinchSpec { case object CustomException extends Exception check { (i: Input, s: String, e: Exception) => - val result = liftAsync[String](IO.raiseError(e)).handle { - case CustomException => Created(s) - }.apply(i).awaitOutput() + val result = liftAsync[String](IO.raiseError(e)) + .handle { case CustomException => + Created(s) + } + .apply(i) + .awaitOutput() result === Some(Left(e)) } } @@ -264,10 +263,15 @@ class EndpointSpec extends FinchSpec { val i = Input.get("/") Seq( - param("foo"), header("foo"), cookie("foo").map(_.value), - multipartFileUpload("foo").map(_.fileName), paramsNel("foo").map(_.toList.mkString), - paramsNel("foor").map(_.toList.mkString), binaryBody.map(new String(_)), stringBody - ).foreach { ii => ii(i).awaitValue() shouldBe Some(Left(Error.NotPresent(ii.item))) } + param("foo"), + header("foo"), + cookie("foo").map(_.value), + multipartFileUpload("foo").map(_.fileName), + paramsNel("foo").map(_.toList.mkString), + paramsNel("foor").map(_.toList.mkString), + binaryBody.map(new String(_)), + stringBody + ).foreach(ii => ii(i).awaitValue() shouldBe Some(Left(Error.NotPresent(ii.item)))) } it should "maps lazily to values" in { @@ -331,7 +335,7 @@ class EndpointSpec extends FinchSpec { val all = a.fold[Set[Error]](e => Set(e), es => es.errors.toList.toSet) ++ - b.fold[Set[Error]](e => Set(e), es => es.errors.toList.toSet) + b.fold[Set[Error]](e => Set(e), es => es.errors.toList.toSet) val Some(Left(first)) = lr(Input.get("/")).awaitValue() val Some(Left(second)) = rl(Input.get("/")).awaitValue() @@ -382,7 +386,7 @@ class EndpointSpec extends FinchSpec { it should "collect errors on Endpoint[Seq[String]] failure" in { val endpoint = params[UUID]("testEndpoint") - an[Errors] shouldBe thrownBy ( + an[Errors] shouldBe thrownBy( endpoint(Input.get("/index", "testEndpoint" -> "a")).awaitValueUnsafe() ) } @@ -395,7 +399,7 @@ class EndpointSpec extends FinchSpec { it should "collect errors on Endpoint[NonEmptyList[String]] failure" in { val endpoint = paramsNel[UUID]("testEndpoint") - an[Errors] shouldBe thrownBy ( + an[Errors] shouldBe thrownBy( endpoint(Input.get("/index", "testEndpoint" -> "a")).awaitValueUnsafe(Duration(10, TimeUnit.SECONDS)) ) } @@ -418,24 +422,23 @@ class EndpointSpec extends FinchSpec { } it should "wrap up an exception thrown inside mapOutputs function" in { - check { (ep: EndpointIO[Int], p: Output.Payload[Int], e: Exception) => { + check { (ep: EndpointIO[Int], p: Output.Payload[Int], e: Exception) => val mappedEndpoint = ep.mapOutput[Int](_ => throw e) val asFunction = mappedEndpoint.asInstanceOf[Output[Int] => IO[Output[Int]]] asFunction.apply(p).attempt.unsafeRunSync() === Left(e) - }} + } } it should "transform F[_] to G[_] effect" in { type W[A] = WriterT[IO, List[String], A] - check { (ep: Endpoint[IO, Int], input: Input) => { + check { (ep: Endpoint[IO, Int], input: Input) => val nat = new (IO ~> W) { def apply[A](fa: IO[A]): WriterT[IO, List[String], A] = WriterT.liftF(fa) } ep.mapK(nat)(input).awaitOutput() === ep(input).awaitOutput() } - } } } diff --git a/core/src/test/scala/io/finch/EntityEndpointLaws.scala b/core/src/test/scala/io/finch/EntityEndpointLaws.scala index 7840489b9..35ebda502 100644 --- a/core/src/test/scala/io/finch/EntityEndpointLaws.scala +++ b/core/src/test/scala/io/finch/EntityEndpointLaws.scala @@ -1,5 +1,7 @@ package io.finch +import scala.reflect.ClassTag + import cats.Eq import cats.effect.Effect import cats.instances.AllInstances @@ -7,9 +9,8 @@ import cats.laws._ import cats.laws.discipline._ import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws -import scala.reflect.ClassTag -abstract class EntityEndpointLaws[F[_] : Effect, A] extends Laws with MissingInstances with AllInstances { +abstract class EntityEndpointLaws[F[_]: Effect, A] extends Laws with MissingInstances with AllInstances { def decoder: DecodeEntity[A] def classTag: ClassTag[A] @@ -26,13 +27,13 @@ abstract class EntityEndpointLaws[F[_] : Effect, A] extends Laws with MissingIns new DefaultRuleSet( name = "evaluating", parent = None, - "roundTrip" -> Prop.forAll { (a: A) => roundTrip(a) } + "roundTrip" -> Prop.forAll((a: A) => roundTrip(a)) ) } object EntityEndpointLaws { - def apply[F[_] : Effect, A: DecodeEntity: ClassTag]( - e: Endpoint[F, Option[A]] + def apply[F[_]: Effect, A: DecodeEntity: ClassTag]( + e: Endpoint[F, Option[A]] )(f: A => Input): EntityEndpointLaws[F, A] = new EntityEndpointLaws[F, A] { val decoder: DecodeEntity[A] = DecodeEntity[A] val classTag: ClassTag[A] = implicitly[ClassTag[A]] diff --git a/core/src/test/scala/io/finch/EvaluatingEndpointLaws.scala b/core/src/test/scala/io/finch/EvaluatingEndpointLaws.scala index 988c63fe5..cd9da0ed1 100644 --- a/core/src/test/scala/io/finch/EvaluatingEndpointLaws.scala +++ b/core/src/test/scala/io/finch/EvaluatingEndpointLaws.scala @@ -5,7 +5,7 @@ import cats.instances.AllInstances import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws -abstract class EvaluatingEndpointLaws[F[_] : Effect, A] extends Laws with MissingInstances with AllInstances { +abstract class EvaluatingEndpointLaws[F[_]: Effect, A] extends Laws with MissingInstances with AllInstances { def decode: DecodeEntity[A] def endpoint(d: DecodeEntity[A]): Endpoint[F, A] @@ -19,7 +19,7 @@ abstract class EvaluatingEndpointLaws[F[_] : Effect, A] extends Laws with Missin def all(implicit a: Arbitrary[Input]): RuleSet = new DefaultRuleSet( name = "all", parent = None, - "doNotEvaluateOnMatch" -> Prop.forAll { (i: Input) => doNotEvaluateOnMatch(i) } + "doNotEvaluateOnMatch" -> Prop.forAll((i: Input) => doNotEvaluateOnMatch(i)) ) } @@ -35,7 +35,7 @@ object EvaluatingEndpointLaws { def evaluated: Boolean = e } - def apply[F[_] : Effect, A: DecodeEntity](e: DecodeEntity[A] => Endpoint[F, A]): EvaluatingEndpointLaws[F, A] = + def apply[F[_]: Effect, A: DecodeEntity](e: DecodeEntity[A] => Endpoint[F, A]): EvaluatingEndpointLaws[F, A] = new EvaluatingEndpointLaws[F, A] { val decode: DecodeEntity[A] = DecodeEntity[A] def endpoint(d: DecodeEntity[A]): Endpoint[F, A] = e(d) diff --git a/core/src/test/scala/io/finch/ExtractPathLaws.scala b/core/src/test/scala/io/finch/ExtractPathLaws.scala index 90b221caf..a5b519045 100644 --- a/core/src/test/scala/io/finch/ExtractPathLaws.scala +++ b/core/src/test/scala/io/finch/ExtractPathLaws.scala @@ -1,13 +1,14 @@ package io.finch +import scala.reflect.ClassTag + import cats.effect.Effect import cats.instances.AllInstances import io.netty.handler.codec.http.QueryStringEncoder import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws -import scala.reflect.ClassTag -abstract class ExtractPathLaws[F[_] : Effect, A] extends Laws with MissingInstances with AllInstances { +abstract class ExtractPathLaws[F[_]: Effect, A] extends Laws with MissingInstances with AllInstances { def decode: DecodePath[A] def one: Endpoint[F, A] def tail: Endpoint[F, List[A]] @@ -21,20 +22,20 @@ abstract class ExtractPathLaws[F[_] : Effect, A] extends Laws with MissingInsta val v = i.route.headOption.flatMap(s => decode(s)) o.awaitValueUnsafe() == v && - (v.isEmpty || o.remainder.contains(i.withRoute(i.route.tail))) + (v.isEmpty || o.remainder.contains(i.withRoute(i.route.tail))) }, "extractTail" -> Prop.forAll { input: Input => val i = input.withRoute(input.route.map(s => new QueryStringEncoder(s).toString)) val o = tail(i) o.awaitValueUnsafe().contains(i.route.flatMap(decode.apply)) && - o.remainder.contains(i.copy(route = Nil)) + o.remainder.contains(i.copy(route = Nil)) } ) } object ExtractPathLaws { - def apply[F[_] : Effect, A: DecodePath: ClassTag]: ExtractPathLaws[F, A] = + def apply[F[_]: Effect, A: DecodePath: ClassTag]: ExtractPathLaws[F, A] = new ExtractPathLaws[F, A] { def tail: Endpoint[F, List[A]] = Endpoint[F].paths[A] def one: Endpoint[F, A] = Endpoint[F].path[A] diff --git a/core/src/test/scala/io/finch/FinchSpec.scala b/core/src/test/scala/io/finch/FinchSpec.scala index 0b8cf9eb4..6ba7bf3d3 100644 --- a/core/src/test/scala/io/finch/FinchSpec.scala +++ b/core/src/test/scala/io/finch/FinchSpec.scala @@ -3,6 +3,8 @@ package io.finch import java.nio.charset.{Charset, StandardCharsets} import java.util.UUID +import scala.reflect.ClassTag + import cats.Eq import cats.data.NonEmptyList import cats.effect.{Effect, IO} @@ -12,26 +14,19 @@ import com.twitter.finagle.http._ import com.twitter.io.Buf import com.twitter.util._ import org.scalacheck.{Arbitrary, Cogen, Gen} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.Checkers import org.typelevel.discipline.Laws -import scala.reflect.ClassTag import shapeless.Witness -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -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 AllInstances with MissingInstances with Endpoint.Module[IO] { - def checkAll(name: String, ruleSet: Laws#RuleSet): Unit = { + def checkAll(name: String, ruleSet: Laws#RuleSet): Unit = for ((id, prop) <- ruleSet.all.properties) it should (name + "." + id) in { check(prop) } - } case class BasicAuthCredentials(user: String, pass: String) @@ -52,8 +47,11 @@ trait FinchSpec extends AnyFlatSpec def genRequestItem: Gen[items.RequestItem] = for { s <- genNonEmptyString i <- Gen.oneOf( - items.BodyItem, items.ParamItem(s), items.HeaderItem(s), - items.MultipleItems, items.CookieItem(s) + items.BodyItem, + items.ParamItem(s), + items.HeaderItem(s), + items.MultipleItems, + items.CookieItem(s) ) } yield i @@ -84,13 +82,9 @@ trait FinchSpec extends AnyFlatSpec value <- genNonEmptyString } yield (key, value) - def genHeaders: Gen[Headers] = Gen.mapOf(genNonEmptyTuple).map(m => - Headers(m.map(kv => kv._1.toLowerCase -> kv._2.toLowerCase)) - ) + def genHeaders: Gen[Headers] = Gen.mapOf(genNonEmptyTuple).map(m => Headers(m.map(kv => kv._1.toLowerCase -> kv._2.toLowerCase))) - def genParams: Gen[Params] = Gen.mapOf(genNonEmptyTuple).map(m => - Params(m.map(kv => kv._1.toLowerCase -> kv._2.toLowerCase)) - ) + def genParams: Gen[Params] = Gen.mapOf(genNonEmptyTuple).map(m => Params(m.map(kv => kv._1.toLowerCase -> kv._2.toLowerCase))) def genCookies: Gen[Cookies] = Gen.listOf(genNonEmptyTuple.map(t => new Cookie(t._1, t._2))).map(Cookies.apply) @@ -99,27 +93,71 @@ trait FinchSpec extends AnyFlatSpec Gen.option(genNonEmptyString).map(OptionalNonEmptyString.apply) def genStatus: Gen[Status] = Gen.oneOf( - Status.Continue, Status.SwitchingProtocols, Status.Processing, Status.Ok, Status.Created, - Status.Accepted, Status.NonAuthoritativeInformation, Status.NoContent, Status.ResetContent, - Status.PartialContent, Status.MultiStatus, Status.MultipleChoices, Status.MovedPermanently, - Status.Found, Status.SeeOther, Status.NotModified, Status.UseProxy, Status.TemporaryRedirect, - Status.BadRequest, Status.Unauthorized, Status.PaymentRequired, Status.Forbidden, - Status.NotFound, Status.MethodNotAllowed, Status.NotAcceptable, - Status.ProxyAuthenticationRequired, Status.RequestTimeout, Status.Conflict, Status.Gone, - Status.LengthRequired, Status.PreconditionFailed, Status.RequestEntityTooLarge, - Status.RequestURITooLong, Status.UnsupportedMediaType, Status.RequestedRangeNotSatisfiable, - Status.ExpectationFailed, Status.EnhanceYourCalm, Status.UnprocessableEntity, Status.Locked, - Status.FailedDependency, Status.UnorderedCollection, Status.UpgradeRequired, - Status.PreconditionRequired, Status.TooManyRequests, Status.RequestHeaderFieldsTooLarge, - Status.ClientClosedRequest, Status.InternalServerError, Status.NotImplemented, - Status.BadGateway, Status.ServiceUnavailable, Status.GatewayTimeout, - Status.HttpVersionNotSupported, Status.VariantAlsoNegotiates, Status.InsufficientStorage, - Status.NotExtended, Status.NetworkAuthenticationRequired + Status.Continue, + Status.SwitchingProtocols, + Status.Processing, + Status.Ok, + Status.Created, + Status.Accepted, + Status.NonAuthoritativeInformation, + Status.NoContent, + Status.ResetContent, + Status.PartialContent, + Status.MultiStatus, + Status.MultipleChoices, + Status.MovedPermanently, + Status.Found, + Status.SeeOther, + Status.NotModified, + Status.UseProxy, + Status.TemporaryRedirect, + Status.BadRequest, + Status.Unauthorized, + Status.PaymentRequired, + Status.Forbidden, + Status.NotFound, + Status.MethodNotAllowed, + Status.NotAcceptable, + Status.ProxyAuthenticationRequired, + Status.RequestTimeout, + Status.Conflict, + Status.Gone, + Status.LengthRequired, + Status.PreconditionFailed, + Status.RequestEntityTooLarge, + Status.RequestURITooLong, + Status.UnsupportedMediaType, + Status.RequestedRangeNotSatisfiable, + Status.ExpectationFailed, + Status.EnhanceYourCalm, + Status.UnprocessableEntity, + Status.Locked, + Status.FailedDependency, + Status.UnorderedCollection, + Status.UpgradeRequired, + Status.PreconditionRequired, + Status.TooManyRequests, + Status.RequestHeaderFieldsTooLarge, + Status.ClientClosedRequest, + Status.InternalServerError, + Status.NotImplemented, + Status.BadGateway, + Status.ServiceUnavailable, + Status.GatewayTimeout, + Status.HttpVersionNotSupported, + Status.VariantAlsoNegotiates, + Status.InsufficientStorage, + Status.NotExtended, + Status.NetworkAuthenticationRequired ) def genCharset: Gen[Charset] = Gen.oneOf( - StandardCharsets.ISO_8859_1, StandardCharsets.US_ASCII, StandardCharsets.UTF_8, - StandardCharsets.UTF_16, StandardCharsets.UTF_16BE, StandardCharsets.UTF_16LE + StandardCharsets.ISO_8859_1, + StandardCharsets.US_ASCII, + StandardCharsets.UTF_8, + StandardCharsets.UTF_16, + StandardCharsets.UTF_16BE, + StandardCharsets.UTF_16LE ) def genOutputMeta: Gen[(Status, Option[Charset], Map[String, String], List[Cookie])] = @@ -148,42 +186,56 @@ trait FinchSpec extends AnyFlatSpec ) def genOutput[A: Arbitrary]: Gen[Output[A]] = Gen.oneOf( - genPayloadOutput[A], genFailureOutput, genEmptyOutput + genPayloadOutput[A], + genFailureOutput, + genEmptyOutput ) def genAccept: Gen[Accept] = { def witness[T <: String](implicit w: Witness.Aux[T]): String = w.value - Gen.oneOf( - witness[Application.Json], - witness[Application.AtomXml], - witness[Application.Csv], - witness[Application.Javascript], - witness[Application.OctetStream], - witness[Application.RssXml], - witness[Application.AtomXml], - witness[Application.WwwFormUrlencoded], - witness[Application.Xml], - witness[Text.Plain], - witness[Text.Html], - witness[Text.EventStream] - ).map(s => Accept.fromString(s)) + Gen + .oneOf( + witness[Application.Json], + witness[Application.AtomXml], + witness[Application.Csv], + witness[Application.Javascript], + witness[Application.OctetStream], + witness[Application.RssXml], + witness[Application.AtomXml], + witness[Application.WwwFormUrlencoded], + witness[Application.Xml], + witness[Text.Plain], + witness[Text.Html], + witness[Text.EventStream] + ) + .map(s => Accept.fromString(s)) } def genMethod: Gen[Method] = Gen.oneOf( - Method.Get, Method.Connect, Method.Delete, Method.Head, - Method.Options, Method.Patch, Method.Post, Method.Put, Method.Trace + Method.Get, + Method.Connect, + Method.Delete, + Method.Head, + Method.Options, + Method.Patch, + Method.Post, + Method.Put, + Method.Trace ) def genVersion: Gen[Version] = Gen.oneOf(Version.Http10, Version.Http11) def genPath: Gen[Path] = for { n <- Gen.choose(0, 20) - ss <- Gen.listOfN(n, Gen.oneOf( - Gen.alphaStr.suchThat(_.nonEmpty), - Gen.uuid.map(_.toString), - Gen.posNum[Long].map(_.toString), - Gen.oneOf(true, false).map(_.toString) - )) + ss <- Gen.listOfN( + n, + Gen.oneOf( + Gen.alphaStr.suchThat(_.nonEmpty), + Gen.uuid.map(_.toString), + Gen.posNum[Long].map(_.toString), + Gen.oneOf(true, false).map(_.toString) + ) + ) } yield Path("/" + ss.mkString("/")) def genBuf: Gen[Buf] = for { @@ -195,19 +247,15 @@ trait FinchSpec extends AnyFlatSpec def genTrace: Gen[Trace] = Gen.oneOf( Gen.const(Trace.empty), - Gen.nonEmptyListOf(Gen.alphaStr).map(l => - l.foldLeft(Trace.empty)((t, s) => t.concat(Trace.segment(s))) - ) + Gen.nonEmptyListOf(Gen.alphaStr).map(l => l.foldLeft(Trace.empty)((t, s) => t.concat(Trace.segment(s)))) ) - def genEndpointResult[F[_] : Effect, A](implicit a: Arbitrary[A]): Gen[EndpointResult[F, A]] = { + def genEndpointResult[F[_]: Effect, A](implicit a: Arbitrary[A]): Gen[EndpointResult[F, A]] = { val matched = for { out <- genOutput[A] input <- arbitraryInput.arbitrary trc <- genTrace - } yield { - EndpointResult.Matched(input, trc, Effect[F].delay(out)) - } + } yield EndpointResult.Matched(input, trc, Effect[F].delay(out)) val notMatched = Gen.const(EndpointResult.NotMatched[F]) val methodNotAllowed = genMethod.map(m => EndpointResult.NotMatched.MethodNotAllowed[F](List(m))) Gen.oneOf(matched, notMatched, methodNotAllowed) @@ -237,18 +285,16 @@ trait FinchSpec extends AnyFlatSpec (r.method.toString, r.version.toString, r.path, Buf.ByteArray.Owned.extract(r.content)) } - implicit def arbitraryEndpoint[F[_] : Effect, A](implicit A: Arbitrary[A]): Arbitrary[Endpoint[F, A]] = Arbitrary( + implicit def arbitraryEndpoint[F[_]: Effect, A](implicit A: Arbitrary[A]): Arbitrary[Endpoint[F, A]] = Arbitrary( Gen.oneOf( Gen.const(Endpoint[F].empty[A]), A.arbitrary.map(a => Endpoint[F].const(a)), - Arbitrary.arbitrary[Throwable].map(e => - Endpoint[F].liftOutputAsync(Effect[F].raiseError[Output[A]](e)) - ), + Arbitrary.arbitrary[Throwable].map(e => Endpoint[F].liftOutputAsync(Effect[F].raiseError[Output[A]](e))), /** - * Note that we don't provide instances of arbitrary endpoints wrapping - * `Input => Output[A]` since `Endpoint` isn't actually lawful in this - * respect. - */ + * Note that we don't provide instances of arbitrary endpoints wrapping + * `Input => Output[A]` since `Endpoint` isn't actually lawful in this + * respect. + */ Arbitrary.arbitrary[Input => A].map { f => new Endpoint[F, A] { final def apply(input: Input): Endpoint.Result[F, A] = @@ -259,12 +305,12 @@ trait FinchSpec extends AnyFlatSpec ) /** - * Equality instance for [[io.finch.Endpoint]]. - * - * We attempt to verify that two endpoints are the same by applying them to a - * fixed number of randomly generated inputs. - */ - implicit def eqEndpoint[F[_] : Effect, A: Eq]: Eq[Endpoint[F, A]] = new Eq[Endpoint[F, A]] { + * Equality instance for [[io.finch.Endpoint]]. + * + * We attempt to verify that two endpoints are the same by applying them to a + * fixed number of randomly generated inputs. + */ + implicit def eqEndpoint[F[_]: Effect, A: Eq]: 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 { @@ -272,9 +318,11 @@ trait FinchSpec extends AnyFlatSpec o <- result.awaitOutput() } yield (r, o) - private[this] def inputs: Stream[Input] = Stream.continually( - Arbitrary.arbitrary[Input].sample - ).flatten + 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)) @@ -284,18 +332,17 @@ trait FinchSpec extends AnyFlatSpec } } - implicit def arbitraryEndpointResult[F[_] : Effect, A](implicit A: Arbitrary[A]): Arbitrary[EndpointResult[F, A]] = + implicit def arbitraryEndpointResult[F[_]: Effect, A](implicit A: Arbitrary[A]): Arbitrary[EndpointResult[F, A]] = Arbitrary(genEndpointResult[F, A]) - implicit def eqEndpointResult[F[_] : Effect, A : Eq]: Eq[EndpointResult[F, A]] = new Eq[EndpointResult[F, A]] { + implicit def eqEndpointResult[F[_]: Effect, A: Eq]: 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 { r <- result.remainder o <- result.awaitOutput() } yield (r, o) - def eqv(x: EndpointResult[F, A], y: EndpointResult[F, A]): Boolean = { + def eqv(x: EndpointResult[F, A], y: EndpointResult[F, A]): Boolean = Eq[Option[(Input, Either[Throwable, Output[A]])]].eqv(await(x), await(y)) - } } implicit def arbitraryInput: Arbitrary[Input] = diff --git a/core/src/test/scala/io/finch/HeaderSpec.scala b/core/src/test/scala/io/finch/HeaderSpec.scala index c629d3379..d922f1a66 100644 --- a/core/src/test/scala/io/finch/HeaderSpec.scala +++ b/core/src/test/scala/io/finch/HeaderSpec.scala @@ -2,8 +2,8 @@ package io.finch import java.util.UUID -import cats.{Eq, Show} import cats.effect.IO +import cats.{Eq, Show} import io.finch.data.Foo import org.scalacheck.Arbitrary @@ -11,11 +11,9 @@ class HeaderSpec extends FinchSpec { behavior of "header*" - def withHeader[A : Show](k: String)(v: A): Input = Input.get("/").withHeaders(k -> Show[A].show(v)) + def withHeader[A: Show](k: String)(v: A): Input = Input.get("/").withHeaders(k -> Show[A].show(v)) - checkAll("Header[String]", - EntityEndpointLaws[IO, String](headerOption("x"))(withHeader("x")) - .evaluating(Arbitrary(genNonEmptyString), Eq[String])) + checkAll("Header[String]", EntityEndpointLaws[IO, String](headerOption("x"))(withHeader("x")).evaluating(Arbitrary(genNonEmptyString), Eq[String])) checkAll("Header[Int]", EntityEndpointLaws[IO, Int](headerOption("x"))(withHeader("x")).evaluating) checkAll("Header[Long]", EntityEndpointLaws[IO, Long](headerOption("x"))(withHeader("x")).evaluating) checkAll("Header[Boolean]", EntityEndpointLaws[IO, Boolean](headerOption("x"))(withHeader("x")).evaluating) diff --git a/core/src/test/scala/io/finch/InputSpec.scala b/core/src/test/scala/io/finch/InputSpec.scala index 26b8512fe..fd5a78d03 100644 --- a/core/src/test/scala/io/finch/InputSpec.scala +++ b/core/src/test/scala/io/finch/InputSpec.scala @@ -16,15 +16,15 @@ class InputSpec extends FinchSpec { it should "properly construct Inputs using factories with params for the different methods" in { def validateInput( - input: Input, - method: Method, - segments: Seq[String], - params: Map[String, String] + input: Input, + method: Method, + segments: Seq[String], + params: Map[String, String] ): Boolean = input.request.method === method && - input.request.path === "/" + segments.mkString("/") && - input.request.params === params && - input.route === segments + input.request.path === "/" + segments.mkString("/") && + input.request.params === params && + input.route === segments check { (ps: Params, p: Path) => val segments = p.p.split("/").toList.drop(1) @@ -53,7 +53,7 @@ class InputSpec extends FinchSpec { def loop(from: List[Buf]): Future[Unit] = from match { case h :: t => p.write(h).before(loop(t)) - case _ => p.close() + case _ => p.close() } loop(s) @@ -79,7 +79,7 @@ class InputSpec extends FinchSpec { it should "add headers through withHeaders" in { check { (i: Input, hs: Headers) => val hm = i.withHeaders(hs.m.toSeq: _*).request.headerMap - hs.m.forall { case (k, v) => hm.contains(k) && hm(k) === v} + hs.m.forall { case (k, v) => hm.contains(k) && hm(k) === v } } } diff --git a/core/src/test/scala/io/finch/MethodSpec.scala b/core/src/test/scala/io/finch/MethodSpec.scala index c4c453b6f..10d20ec9d 100644 --- a/core/src/test/scala/io/finch/MethodSpec.scala +++ b/core/src/test/scala/io/finch/MethodSpec.scala @@ -1,17 +1,16 @@ package io.finch +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Future => ScalaFuture} + import cats.Id import cats.effect.IO import com.twitter.finagle.http.Response import com.twitter.util.{Future => TwitterFuture} import org.scalacheck.Arbitrary import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks -import scala.concurrent.{Future => ScalaFuture} -import scala.concurrent.ExecutionContext.Implicits.global -class MethodSpec - extends FinchSpec - with ScalaCheckDrivenPropertyChecks { +class MethodSpec extends FinchSpec with ScalaCheckDrivenPropertyChecks { behavior of "method" @@ -19,36 +18,36 @@ class MethodSpec Arbitrary(genOutput[String].map(_.toResponse[Id, Text.Plain])) it should "map Output value to endpoint" in { - checkValue((i: String) => get(zero) { Ok(i) }) + checkValue((i: String) => get(zero)(Ok(i))) } it should "map Response value to endpoint" in { - checkValue((i: Response) => get(zero) { i }) + checkValue((i: Response) => get(zero)(i)) } it should "map F[Output[A]] value to endpoint" in { - checkValue((i: String) => get(zero) { IO.pure(Ok(i)) }) + checkValue((i: String) => get(zero)(IO.pure(Ok(i)))) } it should "map TwitterFuture[Output[A]] value to endpoint" in { - checkValue((i: String) => get(zero) { TwitterFuture.value(Ok(i)) } ) + checkValue((i: String) => get(zero)(TwitterFuture.value(Ok(i)))) } it should "map ScalaFuture[Output[A]] value to endpoint" in { - checkValue((i: String) => get(zero) { ScalaFuture.successful(Ok(i)) } ) + checkValue((i: String) => get(zero)(ScalaFuture.successful(Ok(i)))) } it should "map F[Response] value to endpoint" in { - checkValue((i: Response) => get(zero) { IO.pure(Ok(i).toResponse[Id, Text.Plain]) }) + checkValue((i: Response) => get(zero)(IO.pure(Ok(i).toResponse[Id, Text.Plain]))) } it should "map TwitterFuture[Response] value to endpoint" in { - checkValue((i: Response) => get(zero) { TwitterFuture.value(Ok(i).toResponse[Id, Text.Plain]) }) + checkValue((i: Response) => get(zero)(TwitterFuture.value(Ok(i).toResponse[Id, Text.Plain]))) } it should "map ScalaFuture[Response] value to endpoint" in { - checkValue((i: Response) => get(zero) { ScalaFuture.successful(Ok(i).toResponse[Id, Text.Plain]) }) + checkValue((i: Response) => get(zero)(ScalaFuture.successful(Ok(i).toResponse[Id, Text.Plain]))) } it should "map A => Output function to endpoint" in { @@ -84,50 +83,53 @@ class MethodSpec } it should "map (A, B) => Output function to endpoint" in { - checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => Ok(s"$x$y") }) + checkFunction2(get(path[Int] :: path[Int])((x: Int, y: Int) => Ok(s"$x$y"))) } it should "map (A, B) => Response function to endpoint" in { - checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => Ok(s"$x$y").toResponse[Id, Text.Plain] }) + checkFunction2(get(path[Int] :: path[Int])((x: Int, y: Int) => Ok(s"$x$y").toResponse[Id, Text.Plain])) } it should "map (A, B) => F[Output[String]] function to endpoint" in { - checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => IO.pure(Ok(s"$x$y")) }) + checkFunction2(get(path[Int] :: path[Int])((x: Int, y: Int) => IO.pure(Ok(s"$x$y")))) } it should "map (A, B) => TwitterFuture[Output[String]] function to endpoint" in { - checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => TwitterFuture.value(Ok(s"$x$y")) }) + checkFunction2(get(path[Int] :: path[Int])((x: Int, y: Int) => TwitterFuture.value(Ok(s"$x$y")))) } it should "map (A, B) => ScalaFuture[Output[String]] function to endpoint" in { - checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => ScalaFuture.successful(Ok(s"$x$y")) }) + checkFunction2(get(path[Int] :: path[Int])((x: Int, y: Int) => ScalaFuture.successful(Ok(s"$x$y")))) } it should "map (A, B) => F[Response] function to endpoint" in { checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => - IO.pure(Ok(s"$x$y").toResponse[Id, Text.Plain]) }) + IO.pure(Ok(s"$x$y").toResponse[Id, Text.Plain]) + }) } it should "map (A, B) => TwitterFuture[Response] function to endpoint" in { checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => - TwitterFuture.value(Ok(s"$x$y").toResponse[Id, Text.Plain]) }) + TwitterFuture.value(Ok(s"$x$y").toResponse[Id, Text.Plain]) + }) } it should "map (A, B) => ScalaFuture[Response] function to endpoint" in { checkFunction2(get(path[Int] :: path[Int]) { (x: Int, y: Int) => - ScalaFuture.successful(Ok(s"$x$y").toResponse[Id, Text.Plain]) }) + ScalaFuture.successful(Ok(s"$x$y").toResponse[Id, Text.Plain]) + }) } behavior of "Custom Type Program[_]" case class Program[A](value: A) - implicit val conv = new ToAsync[Program,IO] { + implicit val conv = new ToAsync[Program, IO] { def apply[A](a: Program[A]): IO[A] = IO(a.value) } it should "map Program[Output[_]] value to endpoint" in { - checkValue((i: String) => get(zero) { Program(Ok(i)) }) + checkValue((i: String) => get(zero)(Program(Ok(i)))) } it should "map A => Program[Output[_]] function to endpoint" in { @@ -141,7 +143,7 @@ class MethodSpec } it should "map Program[Response] value to endpoint" in { - checkValue((i: Response) => get(zero) { Program(i) }) + checkValue((i: Response) => get(zero)(Program(i))) } it should "map A => Program[Response] function to endpoint" in { @@ -154,30 +156,27 @@ class MethodSpec }) } - private def checkValue[A : Arbitrary](f: A => Endpoint[IO, A]): Unit = { - forAll((input: A) => { + private def checkValue[A: Arbitrary](f: A => Endpoint[IO, A]): Unit = + forAll { (input: A) => val e = f(input) e(Input.get("/")).awaitValueUnsafe() shouldBe Some(input) - }) - } + } - private def checkFunction(e: Endpoint[IO, _]): Unit = { - forAll((input: Int) => { + private def checkFunction(e: Endpoint[IO, _]): Unit = + forAll { (input: Int) => e(Input.get(s"/$input")).awaitValueUnsafe() match { case Some(r: Response) => r.contentString shouldBe input.toString - case Some(a: Int) => a shouldBe input - case _ => () + case Some(a: Int) => a shouldBe input + case _ => () } - }) - } + } - private def checkFunction2(e: Endpoint[IO, _]): Unit = { - forAll((x: Int, y: Int) => { + private def checkFunction2(e: Endpoint[IO, _]): Unit = + forAll { (x: Int, y: Int) => e(Input.get(s"/$x/$y")).awaitValueUnsafe() match { case Some(r: Response) => r.contentString shouldBe s"$x$y" - case Some(a: String) => a shouldBe s"$x$y" - case _ => () + case Some(a: String) => a shouldBe s"$x$y" + case _ => () } - }) - } + } } diff --git a/core/src/test/scala/io/finch/MissingInstances.scala b/core/src/test/scala/io/finch/MissingInstances.scala index 22a9bedb4..a3cceaab5 100644 --- a/core/src/test/scala/io/finch/MissingInstances.scala +++ b/core/src/test/scala/io/finch/MissingInstances.scala @@ -1,18 +1,19 @@ package io.finch +import scala.concurrent.ExecutionContext + import cats.Eq import cats.effect.{ContextShift, IO} import com.twitter.io.Buf -import scala.concurrent.ExecutionContext /** - * Type class instances for non-Finch types. - */ + * Type class instances for non-Finch types. + */ trait MissingInstances { implicit def eqEither[A](implicit A: Eq[A]): Eq[Either[Throwable, A]] = Eq.instance { case (Right(a), Right(b)) => A.eqv(a, b) - case (Left(x), Left(y)) => x == y - case _ => false + case (Left(x), Left(y)) => x == y + case _ => false } implicit def eqBuf: Eq[Buf] = Eq.fromUniversalEquals diff --git a/core/src/test/scala/io/finch/MultipartSpec.scala b/core/src/test/scala/io/finch/MultipartSpec.scala index 0eee087f1..04ef3d897 100644 --- a/core/src/test/scala/io/finch/MultipartSpec.scala +++ b/core/src/test/scala/io/finch/MultipartSpec.scala @@ -4,8 +4,8 @@ import java.util.UUID import cats.Show import cats.effect.IO -import com.twitter.finagle.http.{FileElement, RequestBuilder, SimpleElement} import com.twitter.finagle.http.exp.Multipart +import com.twitter.finagle.http.{FileElement, RequestBuilder, SimpleElement} import com.twitter.io.Buf import io.finch.data.Foo @@ -14,40 +14,26 @@ class MultipartSpec extends FinchSpec { behavior of "multipart*" def withFileUpload(name: String, value: Buf): Input = - Input.fromRequest(RequestBuilder() - .url("http://example.com") - .add(FileElement(name, value, Some("image/gif"), Some("dealwithit.gif"))) - .buildFormPost(multipart = true) + Input.fromRequest( + RequestBuilder().url("http://example.com").add(FileElement(name, value, Some("image/gif"), Some("dealwithit.gif"))).buildFormPost(multipart = true) ) - def withAttribute[A : Show](first: (String, A), rest: (String, A)*): Input = { - val req = RequestBuilder() - .url("http://example.com") - .add(SimpleElement(first._1, Show[A].show(first._2))) + def withAttribute[A: Show](first: (String, A), rest: (String, A)*): Input = { + val req = RequestBuilder().url("http://example.com").add(SimpleElement(first._1, Show[A].show(first._2))) Input.fromRequest( - rest.foldLeft(req)((builder, attr) => - builder.add(SimpleElement(attr._1, Show[A].show(attr._2))) - ).buildFormPost(multipart = true) + rest.foldLeft(req)((builder, attr) => builder.add(SimpleElement(attr._1, Show[A].show(attr._2)))).buildFormPost(multipart = true) ) } - checkAll("Attribute[String]", - EntityEndpointLaws[IO, String](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Int]", - EntityEndpointLaws[IO, Int](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Long]", - EntityEndpointLaws[IO, Long](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Boolean]", - EntityEndpointLaws[IO, Boolean](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Float]", - EntityEndpointLaws[IO, Float](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Double]", - EntityEndpointLaws[IO, Double](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[UUID]", - EntityEndpointLaws[IO, UUID](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) - checkAll("Attribute[Foo]", - EntityEndpointLaws[IO, Foo](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[String]", EntityEndpointLaws[IO, String](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Int]", EntityEndpointLaws[IO, Int](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Long]", EntityEndpointLaws[IO, Long](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Boolean]", EntityEndpointLaws[IO, Boolean](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Float]", EntityEndpointLaws[IO, Float](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Double]", EntityEndpointLaws[IO, Double](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[UUID]", EntityEndpointLaws[IO, UUID](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) + checkAll("Attribute[Foo]", EntityEndpointLaws[IO, Foo](multipartAttributeOption("x"))(a => withAttribute("x" -> a)).evaluating) checkAll( "EvaluatingAttribute[String]", diff --git a/core/src/test/scala/io/finch/OutputSpec.scala b/core/src/test/scala/io/finch/OutputSpec.scala index 136eba65a..2888c7ed1 100644 --- a/core/src/test/scala/io/finch/OutputSpec.scala +++ b/core/src/test/scala/io/finch/OutputSpec.scala @@ -1,10 +1,12 @@ package io.finch +import java.nio.charset.{Charset, StandardCharsets} + +import scala.util.{Failure, Success, Try} + import cats.Id import com.twitter.finagle.http.Status import com.twitter.io.Buf -import java.nio.charset.{Charset, StandardCharsets} -import scala.util.{Failure, Success, Try} class OutputSpec extends FinchSpec { @@ -102,7 +104,7 @@ class OutputSpec extends FinchSpec { check { e: Output.Empty => Try(e.value) match { case Failure(f) => f.getMessage === "empty output" - case _ => false + case _ => false } } } @@ -111,7 +113,7 @@ class OutputSpec extends FinchSpec { check { f: Output.Failure => Try(f.value) match { case Failure(ex) => ex.getMessage === f.cause.getMessage - case _ => false + case _ => false } } } diff --git a/core/src/test/scala/io/finch/ParamSpec.scala b/core/src/test/scala/io/finch/ParamSpec.scala index 1f7d56cdf..4842b4954 100644 --- a/core/src/test/scala/io/finch/ParamSpec.scala +++ b/core/src/test/scala/io/finch/ParamSpec.scala @@ -10,7 +10,7 @@ class ParamSpec extends FinchSpec { behavior of "param*" - def withParam[A : Show](k: String)(v: A): Input = Input.get("/", k -> Show[A].show(v)) + def withParam[A: Show](k: String)(v: A): Input = Input.get("/", k -> Show[A].show(v)) checkAll("Param[String]", EntityEndpointLaws[IO, String](paramOption("x"))(withParam("x")).evaluating) checkAll("Param[Int]", EntityEndpointLaws[IO, Int](paramOption("x"))(withParam("x")).evaluating) @@ -42,14 +42,14 @@ class ParamSpec extends FinchSpec { it should "collect errors on Endpoint[Seq[String]] failure" in { val endpoint = params[UUID]("testEndpoint") - an[Errors] shouldBe thrownBy ( + an[Errors] shouldBe thrownBy( endpoint(Input.get("/index", "testEndpoint" -> "a")).awaitValueUnsafe() ) } it should "collect errors on Endpoint[NonEmptyList[String]] failure" in { val endpoint = paramsNel[UUID]("testEndpoint") - an[Errors] shouldBe thrownBy ( + an[Errors] shouldBe thrownBy( endpoint(Input.get("/index", "testEndpoint" -> "a")).awaitValueUnsafe() ) } diff --git a/core/src/test/scala/io/finch/ServerSentEventSpec.scala b/core/src/test/scala/io/finch/ServerSentEventSpec.scala index 89016b450..d2e7c0ae5 100644 --- a/core/src/test/scala/io/finch/ServerSentEventSpec.scala +++ b/core/src/test/scala/io/finch/ServerSentEventSpec.scala @@ -1,16 +1,17 @@ package io.finch +import java.nio.charset.{Charset, StandardCharsets} + import cats.Show import com.twitter.concurrent.AsyncStream import com.twitter.io.Buf import io.finch.internal.HttpContent -import java.nio.charset.{Charset, StandardCharsets} -import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Gen.Choose import org.scalacheck.Prop.propBoolean -import org.scalatestplus.scalacheck.Checkers +import org.scalacheck.{Arbitrary, Gen} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.Checkers class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { @@ -21,14 +22,18 @@ class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { import ServerSentEvent._ def genCharset: Gen[Charset] = Gen.oneOf( - StandardCharsets.ISO_8859_1, StandardCharsets.US_ASCII, StandardCharsets.UTF_8, - StandardCharsets.UTF_16, StandardCharsets.UTF_16BE, StandardCharsets.UTF_16LE + StandardCharsets.ISO_8859_1, + StandardCharsets.US_ASCII, + StandardCharsets.UTF_8, + StandardCharsets.UTF_16, + StandardCharsets.UTF_16BE, + StandardCharsets.UTF_16LE ) implicit def arbitraryCharset: Arbitrary[Charset] = Arbitrary(genCharset) def dataOnlySse: Gen[ServerSentEvent[String]] = for { - data <- Gen.alphaStr + data <- Gen.alphaStr } yield ServerSentEvent(data) def sseWithId: Gen[ServerSentEvent[String]] = for { @@ -50,7 +55,6 @@ class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { strs <- Gen.nonEmptyListOf(dataOnlySse) } yield AsyncStream.fromSeq(strs) - implicit def arbitrarySse: Arbitrary[AsyncStream[ServerSentEvent[String]]] = Arbitrary(streamDataOnlyGenerator) @@ -73,9 +77,14 @@ class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { (event.event.isDefined && event.id.isEmpty && event.retry.isEmpty) ==> { val encoded = encoder(event, cs) val actualText = encoded.asString(cs) - val expectedParts = Buf(Vector( - text("data:", cs), text(event.data, cs), text("\n", cs), text(s"event:${event.event.get}\n", cs) - )) + val expectedParts = Buf( + Vector( + text("data:", cs), + text(event.data, cs), + text("\n", cs), + text(s"event:${event.event.get}\n", cs) + ) + ) actualText === expectedParts.asString(cs) } } @@ -88,9 +97,14 @@ class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { (event.event.isEmpty && event.id.isDefined && event.retry.isEmpty) ==> { val encoded = encoder(event, cs) val actualText = encoded.asString(cs) - val expectedParts = Buf(Vector( - text("data:", cs), text(event.data, cs), text("\n", cs), text(s"id:${event.id.get}\n", cs) - )) + val expectedParts = Buf( + Vector( + text("data:", cs), + text(event.data, cs), + text("\n", cs), + text(s"id:${event.id.get}\n", cs) + ) + ) actualText === expectedParts.asString(cs) } } @@ -103,9 +117,14 @@ class ServerSentEventSpec extends AnyFlatSpec with Matchers with Checkers { (event.event.isEmpty && event.id.isEmpty && event.retry.isDefined) ==> { val encoded = encoder(event, cs) val actualText = encoded.asString(cs) - val expectedParts = Buf(Vector( - text("data:", cs), text(event.data, cs), text("\n", cs), text(s"retry:${event.retry.get}\n", cs) - )) + val expectedParts = Buf( + Vector( + text("data:", cs), + text(event.data, cs), + text("\n", cs), + text(s"retry:${event.retry.get}\n", cs) + ) + ) actualText === expectedParts.asString(cs) } } diff --git a/core/src/test/scala/io/finch/StreamingLaws.scala b/core/src/test/scala/io/finch/StreamingLaws.scala index 876be728f..547568b26 100644 --- a/core/src/test/scala/io/finch/StreamingLaws.scala +++ b/core/src/test/scala/io/finch/StreamingLaws.scala @@ -17,7 +17,7 @@ abstract class StreamingLaws[S[_[_], _], F[_]] extends Laws with AllInstances wi implicit def F: Effect[F] def toResponse: ToResponse.Aux[F, S[F, Buf], Text.Plain] - def fromList: List[Buf] => S[F, Buf] + def fromList: List[Buf] => S[F, Buf] def toList: S[F, Array[Byte]] => List[Buf] def roundTrip(a: List[Buf], cs: Charset): IsEq[List[Buf]] = { @@ -28,28 +28,20 @@ abstract class StreamingLaws[S[_[_], _], F[_]] extends Laws with AllInstances wi Pipe.copy(rep.reader, req.writer).ensure(req.writer.close()) - Endpoint - .binaryBodyStream[F, S] - .apply(Input.fromRequest(req)) - .awaitValueUnsafe() - .map(toList) - .get <-> a + Endpoint.binaryBodyStream[F, S].apply(Input.fromRequest(req)).awaitValueUnsafe().map(toList).get <-> a } - def onlyChunked: EndpointResult[F, S[F, Array[Byte]]] = { - Endpoint - .binaryBodyStream[F, S] - .apply(Input.fromRequest(Request())) - } + def onlyChunked: EndpointResult[F, S[F, Array[Byte]]] = + Endpoint.binaryBodyStream[F, S].apply(Input.fromRequest(Request())) def all(implicit - arb: Arbitrary[Buf], - CS: Arbitrary[Charset] + arb: Arbitrary[Buf], + CS: Arbitrary[Charset] ): RuleSet = new DefaultRuleSet( name = "all", parent = None, - "roundTrip" -> Prop.forAll { (a: List[Buf], cs: Charset) => roundTrip(a, cs) }, + "roundTrip" -> Prop.forAll((a: List[Buf], cs: Charset) => roundTrip(a, cs)), "onlyChunked" -> Prop.=?(EndpointResult.NotMatched[F], onlyChunked) ) } @@ -57,12 +49,12 @@ abstract class StreamingLaws[S[_[_], _], F[_]] extends Laws with AllInstances wi object StreamingLaws { def apply[S[_[_], _], F[_]]( - streamFromList: List[Buf] => S[F, Buf], - listFromStream: S[F, Array[Byte]] => List[Buf] + streamFromList: List[Buf] => S[F, Buf], + listFromStream: S[F, Array[Byte]] => List[Buf] )(implicit - f: Effect[F], - lr: LiftReader[S, F], - tr: ToResponse.Aux[F, S[F, Buf], Text.Plain] + f: Effect[F], + lr: LiftReader[S, F], + tr: ToResponse.Aux[F, S[F, Buf], Text.Plain] ): StreamingLaws[S, F] = new StreamingLaws[S, F] { implicit val LR: LiftReader[S, F] = lr implicit val F: Effect[F] = f diff --git a/core/src/test/scala/io/finch/data/Foo.scala b/core/src/test/scala/io/finch/data/Foo.scala index fcfbef917..b83c090a4 100644 --- a/core/src/test/scala/io/finch/data/Foo.scala +++ b/core/src/test/scala/io/finch/data/Foo.scala @@ -2,8 +2,8 @@ package io.finch.data import cats.{Eq, Show} import com.twitter.io.Buf -import io.finch.{Application, Decode, Encode} import io.finch.internal.HttpContent +import io.finch.{Application, Decode, Encode} import org.scalacheck.{Arbitrary, Gen} case class Foo(s: String) @@ -17,15 +17,13 @@ object Foo { Decode.text((b, cs) => Right(Foo(b.asString(cs)))) implicit val decodeCsvFoo: Decode.Aux[Foo, Application.Csv] = - Decode.instance((b, cs) => - Right(Foo(b.asString(cs).split("\n").last)) - ) + Decode.instance((b, cs) => Right(Foo(b.asString(cs).split("\n").last))) implicit val encodeCsvFoo: Encode.Aux[Foo, Application.Csv] = Encode.instance((a, cs) => Buf.ByteArray.Owned( - s"""|column - |${a.s}""".stripMargin.getBytes(cs) + s"""|column + |${a.s}""".stripMargin.getBytes(cs) ) ) @@ -35,4 +33,3 @@ object Foo { implicit val arbitraryFoo: Arbitrary[Foo] = Arbitrary(Gen.alphaStr.suchThat(_.nonEmpty).map(Foo.apply)) } - diff --git a/core/src/test/scala/io/finch/internal/HttpContentSpec.scala b/core/src/test/scala/io/finch/internal/HttpContentSpec.scala index 2e3f233aa..0d62ccb87 100644 --- a/core/src/test/scala/io/finch/internal/HttpContentSpec.scala +++ b/core/src/test/scala/io/finch/internal/HttpContentSpec.scala @@ -1,8 +1,9 @@ package io.finch.internal +import java.nio.charset.Charset + import com.twitter.io.Buf import io.finch.FinchSpec -import java.nio.charset.Charset class HttpContentSpec extends FinchSpec { diff --git a/core/src/test/scala/io/finch/internal/HttpMessageSpec.scala b/core/src/test/scala/io/finch/internal/HttpMessageSpec.scala index 5d1f63b38..504282410 100644 --- a/core/src/test/scala/io/finch/internal/HttpMessageSpec.scala +++ b/core/src/test/scala/io/finch/internal/HttpMessageSpec.scala @@ -1,14 +1,15 @@ package io.finch.internal +import java.nio.charset.{Charset, StandardCharsets} + import com.twitter.finagle.http.Request import io.finch.FinchSpec -import java.nio.charset.{Charset, StandardCharsets} class HttpMessageSpec extends FinchSpec { def slowCharset(req: Request): Charset = req.charset match { case Some(cs) => Charset.forName(cs) - case None => StandardCharsets.UTF_8 + case None => StandardCharsets.UTF_8 } behavior of "HttpMessage" diff --git a/core/src/test/scala/io/finch/internal/ToEffectLaws.scala b/core/src/test/scala/io/finch/internal/ToEffectLaws.scala index 20ba8274e..13a59bd09 100644 --- a/core/src/test/scala/io/finch/internal/ToEffectLaws.scala +++ b/core/src/test/scala/io/finch/internal/ToEffectLaws.scala @@ -1,9 +1,9 @@ package io.finch.internal -import cats.{Applicative, Eq} import cats.instances.AllInstances import cats.laws._ import cats.laws.discipline._ +import cats.{Applicative, Eq} import io.finch.MissingInstances import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws @@ -16,9 +16,8 @@ abstract trait ToEffectLaws[F[_], G[_], A] extends Laws with MissingInstances wi def T: ToAsync[F, G] - def nTransformation(f: => F[A], g: G[A], fn: A => A): IsEq[A] = { + def nTransformation(f: => F[A], g: G[A], fn: A => A): IsEq[A] = extract(G.map(T(f))(fn)) <-> extract(T(F.map(f)(fn))) - } def all(implicit A: Arbitrary[A], E: Eq[A]): RuleSet = new DefaultRuleSet( name = "all", @@ -30,8 +29,8 @@ abstract trait ToEffectLaws[F[_], G[_], A] extends Laws with MissingInstances wi object ToEffectLaws { - def apply[F[_] : Applicative, G[_] : Applicative, A](e: G[A] => A)(implicit - t: ToAsync[F, G] + def apply[F[_]: Applicative, G[_]: Applicative, A](e: G[A] => A)(implicit + t: ToAsync[F, G] ): ToEffectLaws[F, G, A] = new ToEffectLaws[F, G, A] { val F: Applicative[F] = implicitly[Applicative[F]] diff --git a/core/src/test/scala/io/finch/internal/ToEffectSpec.scala b/core/src/test/scala/io/finch/internal/ToEffectSpec.scala index 95a5e40bb..9210e42fa 100644 --- a/core/src/test/scala/io/finch/internal/ToEffectSpec.scala +++ b/core/src/test/scala/io/finch/internal/ToEffectSpec.scala @@ -1,11 +1,12 @@ package io.finch.internal +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Future => ScalaFuture} + import cats.Applicative import cats.effect.IO import com.twitter.util.{Future => TwitterFuture} import io.finch.FinchSpec -import scala.concurrent.{Future => ScalaFuture} -import scala.concurrent.ExecutionContext.Implicits.global class ToEffectSpec extends FinchSpec { diff --git a/core/src/test/scala/io/finch/internal/TooFastStringSpec.scala b/core/src/test/scala/io/finch/internal/TooFastStringSpec.scala index 5dcdc561c..ee5a248b4 100644 --- a/core/src/test/scala/io/finch/internal/TooFastStringSpec.scala +++ b/core/src/test/scala/io/finch/internal/TooFastStringSpec.scala @@ -3,9 +3,9 @@ package io.finch.internal import com.twitter.util.Try import org.scalacheck.Gen import org.scalacheck.Prop.forAll -import org.scalatestplus.scalacheck.Checkers import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.Checkers class TooFastStringSpec extends AnyFlatSpec with Matchers with Checkers { diff --git a/examples/src/main/scala/io/finch/div/Main.scala b/examples/src/main/scala/io/finch/div/Main.scala index c94c4753a..80212d841 100644 --- a/examples/src/main/scala/io/finch/div/Main.scala +++ b/examples/src/main/scala/io/finch/div/Main.scala @@ -6,29 +6,29 @@ import com.twitter.util.Await import io.finch._ /** - * A tiny Finch application that serves a single endpoint `POST /:a/b:` that divides `a` by `b`. - * - * Use the following sbt command to run the application. - * - * {{{ - * $ sbt 'examples/runMain io.finch.div.Main' - * }}} - * - * Use the following HTTPie commands to test endpoints. - * - * {{{ - * $ http POST :8081/20/10 - * $ http POST :8081/10/0 - * }}} - */ + * A tiny Finch application that serves a single endpoint `POST /:a/b:` that divides `a` by `b`. + * + * Use the following sbt command to run the application. + * + * {{{ + * $ sbt 'examples/runMain io.finch.div.Main' + * }}} + * + * Use the following HTTPie commands to test endpoints. + * + * {{{ + * $ http POST :8081/20/10 + * $ http POST :8081/10/0 + * }}} + */ object Main extends App with Endpoint.Module[IO] { // We can serve Ints as plain/text responses since there is cats.Show[Int] // available via the cats.instances.int._ import. def div: Endpoint[IO, Int] = post(path[Int] :: path[Int]) { (a: Int, b: Int) => Ok(a / b) - } handle { - case e: ArithmeticException => BadRequest(e) + } handle { case e: ArithmeticException => + BadRequest(e) } Await.ready(Http.server.serve(":8081", div.toServiceAs[Text.Plain])) diff --git a/examples/src/main/scala/io/finch/iteratee/Main.scala b/examples/src/main/scala/io/finch/iteratee/Main.scala index 01aeba340..57e0ca2ac 100644 --- a/examples/src/main/scala/io/finch/iteratee/Main.scala +++ b/examples/src/main/scala/io/finch/iteratee/Main.scala @@ -1,5 +1,7 @@ package io.finch.iteratee +import scala.util.Random + import cats.effect.{ExitCode, IO, IOApp, Resource} import com.twitter.finagle.{Http, ListeningServer} import com.twitter.util.Future @@ -8,7 +10,6 @@ import io.finch._ import io.finch.catsEffect._ import io.finch.circe._ import io.iteratee.{Enumerator, Iteratee} -import scala.util.Random /** * A Finch application featuring iteratee-based streaming support. @@ -50,9 +51,8 @@ object Main extends IOApp { 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 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") { @@ -65,15 +65,11 @@ object Main extends IOApp { } def serve: IO[ListeningServer] = IO( - Http.server - .withStreaming(enabled = true) - .serve(":8081", (sumJson :+: streamJson :+: isPrime).toServiceAs[Application.Json]) + Http.server.withStreaming(enabled = true).serve(":8081", (sumJson :+: streamJson :+: isPrime).toServiceAs[Application.Json]) ) def run(args: List[String]): IO[ExitCode] = { - val server = Resource.make(serve)(s => - IO.suspend(implicitly[ToAsync[Future, IO]].apply(s.close())) - ) + val server = Resource.make(serve)(s => IO.suspend(implicitly[ToAsync[Future, IO]].apply(s.close()))) server.use(_ => IO.never).as(ExitCode.Success) } diff --git a/examples/src/main/scala/io/finch/middleware/Main.scala b/examples/src/main/scala/io/finch/middleware/Main.scala index ffaee4e6e..b169014a2 100644 --- a/examples/src/main/scala/io/finch/middleware/Main.scala +++ b/examples/src/main/scala/io/finch/middleware/Main.scala @@ -1,7 +1,6 @@ package io.finch.middleware import cats.effect.IO -import cats.syntax.apply._ import com.twitter.finagle.Http import com.twitter.finagle.http.{Response, Status} import com.twitter.util.{Await, Time} @@ -32,14 +31,14 @@ object Main extends App with Endpoint.Module[IO] { val auth: Endpoint.Compiled[IO] => Endpoint.Compiled[IO] = compiled => { Endpoint.Compiled[IO] { case req if req.authorization.contains("secret") => compiled(req) - case _ => IO.pure(Trace.empty -> Right(Response(Status.Unauthorized))) + case _ => IO.pure(Trace.empty -> Right(Response(Status.Unauthorized))) } } val logging: Endpoint.Compiled[IO] => Endpoint.Compiled[IO] = compiled => { - compiled.tapWithF((req, res) => { + compiled.tapWithF { (req, res) => IO(print(s"Request: $req\n")) *> IO(print(s"Response: $res\n")) *> IO.pure(res) - }) + } } val stats: Endpoint.Compiled[IO] => Endpoint.Compiled[IO] = compiled => { @@ -51,9 +50,7 @@ object Main extends App with Endpoint.Module[IO] { (trace, response) = traceAndResponse stop <- now _ <- IO(print(s"Response time: ${stop.diff(start)}. Trace: $trace\n")) - } yield { - (trace, response) - } + } yield (trace, response) } } diff --git a/examples/src/main/scala/io/finch/todo/App.scala b/examples/src/main/scala/io/finch/todo/App.scala index 0d03757ed..17df959e8 100644 --- a/examples/src/main/scala/io/finch/todo/App.scala +++ b/examples/src/main/scala/io/finch/todo/App.scala @@ -1,7 +1,7 @@ package io.finch.todo -import cats.effect.{ContextShift, IO} import cats.effect.concurrent.Ref +import cats.effect.{ContextShift, IO} import com.twitter.finagle.Service import com.twitter.finagle.http.{Request, Response, Status} import io.circe.generic.auto._ @@ -9,10 +9,10 @@ import io.finch._ import io.finch.circe._ class App( - idRef: Ref[IO, Int], - storeRef: Ref[IO, Map[Int, Todo]] -)( - implicit S: ContextShift[IO] + idRef: Ref[IO, Int], + storeRef: Ref[IO, Map[Int, Todo]] +)(implicit + S: ContextShift[IO] ) extends Endpoint.Module[IO] { final val postedTodo: Endpoint[IO, Todo] = @@ -41,21 +41,21 @@ class App( } final val getTodos: Endpoint[IO, List[Todo]] = get("todos") { - storeRef.get.map(m => Ok(m.values.toList.sortBy(- _.id))) + storeRef.get.map(m => Ok(m.values.toList.sortBy(-_.id))) } final val deleteTodo: Endpoint[IO, Todo] = delete("todos" :: path[Int]) { id: Int => storeRef.modify { store => store.get(id) match { case Some(t) => (store - id, Ok(t)) - case None => (store, Output.empty(Status.NotFound)) + case None => (store, Output.empty(Status.NotFound)) } } } final val deleteTodos: Endpoint[IO, List[Todo]] = delete("todos") { storeRef.modify { store => - (Map.empty, Ok(store.values.toList.sortBy(- _.id))) + (Map.empty, Ok(store.values.toList.sortBy(-_.id))) } } diff --git a/examples/src/main/scala/io/finch/todo/Main.scala b/examples/src/main/scala/io/finch/todo/Main.scala index 6a7053752..1ba1c68d0 100644 --- a/examples/src/main/scala/io/finch/todo/Main.scala +++ b/examples/src/main/scala/io/finch/todo/Main.scala @@ -1,38 +1,38 @@ package io.finch.todo +import scala.concurrent.ExecutionContext + import cats.effect.IO import cats.effect.concurrent.Ref import com.twitter.app.Flag import com.twitter.finagle.Http import com.twitter.server.TwitterServer import com.twitter.util.Await -import scala.concurrent.ExecutionContext /** - * A simple Finch server serving a TODO application. - * - * Use the following sbt command to run the application. - * - * {{{ - * $ sbt 'examples/runMain io.finch.todo.Main' - * }}} - * - * Open your browser at `http://localhost:8081/todo/index.html` or use the following HTTPie - * commands to test endpoints. - * - * {{{ - * $ http POST :8081/todos title=foo - * $ http PATCH :8081/todos/ completed:=true - * $ http :8081/todos - * $ http DELETE :8081/todos/ - * $ http DELETE :8081/todos - * }}} - */ + * A simple Finch server serving a TODO application. + * + * Use the following sbt command to run the application. + * + * {{{ + * $ sbt 'examples/runMain io.finch.todo.Main' + * }}} + * + * Open your browser at `http://localhost:8081/todo/index.html` or use the following HTTPie + * commands to test endpoints. + * + * {{{ + * $ http POST :8081/todos title=foo + * $ http PATCH :8081/todos/ completed:=true + * $ http :8081/todos + * $ http DELETE :8081/todos/ + * $ http DELETE :8081/todos + * }}} + */ object Main extends TwitterServer { private val port: Flag[Int] = flag("port", 8081, "TCP port for HTTP server") - def main(): Unit = { println(s"Open your browser at http://localhost:${port()}/todo/index.html") //scalastyle:ignore @@ -43,11 +43,11 @@ object Main extends TwitterServer { val app = new App(id, store)(IO.contextShift(ExecutionContext.global)) val srv = Http.server.withStatsReceiver(statsReceiver) - srv.serve(s":${ port() }", app.toService) + srv.serve(s":${port()}", app.toService) } val handle = server.unsafeRunSync() - onExit { handle.close() } + onExit(handle.close()) Await.ready(adminHttpServer) } } diff --git a/examples/src/main/scala/io/finch/wrk/Finagle.scala b/examples/src/main/scala/io/finch/wrk/Finagle.scala index fa3858abf..17b94f95d 100644 --- a/examples/src/main/scala/io/finch/wrk/Finagle.scala +++ b/examples/src/main/scala/io/finch/wrk/Finagle.scala @@ -9,17 +9,17 @@ import com.twitter.util.Future import io.finch.internal._ /** - * How to benchmark this: - * - * 1. Run the server: sbt 'examples/runMain io.finch.wrk.Finagle' - * 2. Run wrk: wrk -t4 -c24 -d30s http://localhost:8081/ - * - * Rule of thumb for picking values for params `t` and `c` (given that `n` is a number of logical - * cores your machine has, including HT): - * - * t = n - * c = t * n * 1.5 - */ + * How to benchmark this: + * + * 1. Run the server: sbt 'examples/runMain io.finch.wrk.Finagle' + * 2. Run wrk: wrk -t4 -c24 -d30s http://localhost:8081/ + * + * Rule of thumb for picking values for params `t` and `c` (given that `n` is a number of logical + * cores your machine has, including HT): + * + * t = n + * c = t * n * 1.5 + */ object Finagle extends Wrk { val mapper: ObjectMapper = new ObjectMapper().registerModule(DefaultScalaModule) diff --git a/examples/src/main/scala/io/finch/wrk/Finch.scala b/examples/src/main/scala/io/finch/wrk/Finch.scala index 12063e1ea..7000397c4 100644 --- a/examples/src/main/scala/io/finch/wrk/Finch.scala +++ b/examples/src/main/scala/io/finch/wrk/Finch.scala @@ -6,17 +6,17 @@ import io.finch._ import io.finch.circe._ /** - * How to benchmark this: - * - * 1. Run the server: sbt 'examples/runMain io.finch.wrk.Finch' - * 2. Run wrk: wrk -t4 -c24 -d30s http://localhost:8081/ - * - * Rule of thumb for picking values for params `t` and `c` (given that `n` is a number of logical - * cores your machine has, including HT): - * - * t = n - * c = t * n * 1.5 - */ + * How to benchmark this: + * + * 1. Run the server: sbt 'examples/runMain io.finch.wrk.Finch' + * 2. Run wrk: wrk -t4 -c24 -d30s http://localhost:8081/ + * + * Rule of thumb for picking values for params `t` and `c` (given that `n` is a number of logical + * cores your machine has, including HT): + * + * t = n + * c = t * n * 1.5 + */ object Finch extends Wrk { serve(Endpoint[IO].lift(Payload("Hello, World!")).toServiceAs[Application.Json]) } diff --git a/examples/src/main/scala/io/finch/wrk/Wrk.scala b/examples/src/main/scala/io/finch/wrk/Wrk.scala index 32352cdcc..bc0db843d 100644 --- a/examples/src/main/scala/io/finch/wrk/Wrk.scala +++ b/examples/src/main/scala/io/finch/wrk/Wrk.scala @@ -1,9 +1,9 @@ package io.finch.wrk -import com.twitter.finagle.{Http, Service} import com.twitter.finagle.http.{Request, Response} import com.twitter.finagle.stats.NullStatsReceiver import com.twitter.finagle.tracing.NullTracer +import com.twitter.finagle.{Http, Service} import com.twitter.util.Await abstract class Wrk extends App { @@ -11,10 +11,6 @@ abstract class Wrk extends App { case class Payload(message: String) protected def serve(s: Service[Request, Response]): Unit = Await.ready( - Http.server - .withCompressionLevel(0) - .withStatsReceiver(NullStatsReceiver) - .withTracer(NullTracer) - .serve(":8081", s) - ) + Http.server.withCompressionLevel(0).withStatsReceiver(NullStatsReceiver).withTracer(NullTracer).serve(":8081", s) + ) } diff --git a/examples/src/test/scala/io/finch/todo/TodoSpec.scala b/examples/src/test/scala/io/finch/todo/TodoSpec.scala index 6fbeb2594..abbcbe3cd 100644 --- a/examples/src/test/scala/io/finch/todo/TodoSpec.scala +++ b/examples/src/test/scala/io/finch/todo/TodoSpec.scala @@ -8,9 +8,9 @@ import io.finch._ import io.finch.circe._ import io.finch.internal.DummyExecutionContext import org.scalacheck.{Arbitrary, Gen} -import org.scalatestplus.scalacheck.Checkers import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.Checkers class TodoSpec extends AnyFlatSpec with Matchers with Checkers { @@ -25,8 +25,8 @@ class TodoSpec extends AnyFlatSpec with Matchers with Checkers { case class AppState(id: Int, store: Map[Int, Todo]) case class TestApp( - id: Ref[IO, Int], - store: Ref[IO, Map[Int, Todo]] + id: Ref[IO, Int], + store: Ref[IO, Map[Int, Todo]] ) extends App(id, store)(IO.contextShift(DummyExecutionContext)) { def state: IO[AppState] = for { i <- id.get @@ -52,19 +52,15 @@ class TodoSpec extends AnyFlatSpec with Matchers with Checkers { it should "post a todo" in { check { (app: TestApp, todo: TodoTitle) => - val input = Input - .post("/todos") - .withBody[Application.Json](todo) + val input = Input.post("/todos").withBody[Application.Json](todo) val shouldBeTrue = for { prev <- app.state posted <- app.postTodo(input).output.get next <- app.state - } yield { - prev.id + 1 == next.id && + } yield prev.id + 1 == next.id && prev.store + (prev.id -> posted.value) == next.store && posted.value == todo.withId(prev.id) - } shouldBeTrue.unsafeRunSync() } @@ -72,22 +68,18 @@ class TodoSpec extends AnyFlatSpec with Matchers with Checkers { it should "patch a todo" in { check { (app: TestApp, todo: TodoCompleted) => - def input(id: Int): Input = Input - .patch(s"/todos/$id") - .withBody[Application.Json](todo) + def input(id: Int): Input = Input.patch(s"/todos/$id").withBody[Application.Json](todo) val shouldBeTrue = for { prev <- app.state patched <- app.patchTodo(input(prev.id - 1)).output.get next <- app.state - } yield { - (prev.id == 0 && patched.status == Status.NotFound && prev == next) || + } yield (prev.id == 0 && patched.status == Status.NotFound && prev == next) || ( next.id == prev.id && patched.value.id == prev.id - 1 && - patched.value.completed == todo.completed && - prev.store + ((prev.id - 1) -> patched.value) == next.store + patched.value.completed == todo.completed && + prev.store + ((prev.id - 1) -> patched.value) == next.store ) - } shouldBeTrue.unsafeRunSync() } diff --git a/fs2/src/main/scala/io/finch/fs2/package.scala b/fs2/src/main/scala/io/finch/fs2/package.scala index 9efb38243..dd743bab7 100644 --- a/fs2/src/main/scala/io/finch/fs2/package.scala +++ b/fs2/src/main/scala/io/finch/fs2/package.scala @@ -1,26 +1,22 @@ package io.finch +import java.nio.charset.Charset + import _root_.fs2.Stream import cats.effect._ import com.twitter.io.{Buf, Pipe, Reader} import com.twitter.util.Future import io.finch.internal._ -import java.nio.charset.Charset package object fs2 extends StreamConcurrentInstances { implicit def streamLiftReader[F[_]](implicit - F: Sync[F], - TA: ToAsync[Future, F] + F: Sync[F], + TA: ToAsync[Future, F] ): LiftReader[Stream, F] = new LiftReader[Stream, F] { - final def apply[A](reader: Reader[Buf], process: Buf => A): Stream[F, A] = { - Stream - .repeatEval(F.suspend(TA(reader.read()))) - .unNoneTerminate - .map(process) - .onFinalize(F.delay(reader.discard())) - } + final def apply[A](reader: Reader[Buf], process: Buf => A): Stream[F, A] = + Stream.repeatEval(F.suspend(TA(reader.read()))).unNoneTerminate.map(process).onFinalize(F.delay(reader.discard())) } implicit def encodeBufConcurrentFs2[F[_]: ConcurrentEffect, CT <: String]: EncodeStream.Aux[F, Stream, Buf, CT] = @@ -32,17 +28,17 @@ package object fs2 extends StreamConcurrentInstances { trait StreamConcurrentInstances extends StreamEffectInstances { implicit def encodeJsonConcurrentFs2Stream[F[_]: ConcurrentEffect, A](implicit - A: Encode.Json[A] + A: Encode.Json[A] ): EncodeStream.Json[F, Stream, A] = new EncodeNewLineDelimitedConcurrentFs2Stream[F, A, Application.Json] implicit def encodeSseConcurrentFs2Stream[F[_]: ConcurrentEffect, A](implicit - A: Encode.Aux[A, Text.EventStream] + A: Encode.Aux[A, Text.EventStream] ): EncodeStream.Aux[F, Stream, A, Text.EventStream] = new EncodeNewLineDelimitedConcurrentFs2Stream[F, A, Text.EventStream] implicit def encodeTextConcurrentFs2Stream[F[_]: ConcurrentEffect, A](implicit - A: Encode.Text[A] + A: Encode.Text[A] ): EncodeStream.Text[F, Stream, A] = new EncodeConcurrentFs2Stream[F, A, Text.Plain] { override protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs) @@ -57,17 +53,17 @@ trait StreamConcurrentInstances extends StreamEffectInstances { trait StreamEffectInstances extends StreamInstances { implicit def encodeJsonEffectFs2Stream[F[_]: Effect, A](implicit - A: Encode.Json[A] + A: Encode.Json[A] ): EncodeStream.Json[F, Stream, A] = new EncodeNewLineDelimitedEffectFs2Stream[F, A, Application.Json] implicit def encodeSseEffectFs2Stream[F[_]: Effect, A](implicit - A: Encode.Aux[A, Text.EventStream] + A: Encode.Aux[A, Text.EventStream] ): EncodeStream.Aux[F, Stream, A, Text.EventStream] = new EncodeNewLineDelimitedEffectFs2Stream[F, A, Text.EventStream] implicit def encodeTextEffectFs2Stream[F[_]: Effect, A](implicit - A: Encode.Text[A] + A: Encode.Text[A] ): EncodeStream.Text[F, Stream, A] = new EncodeEffectFs2Stream[F, A, Text.Plain] { override protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs) @@ -76,36 +72,37 @@ trait StreamEffectInstances extends StreamInstances { trait StreamInstances { - protected final class EncodeNewLineDelimitedConcurrentFs2Stream[F[_]: ConcurrentEffect, A, CT <: String](implicit - A: Encode.Aux[A, CT] + final protected class EncodeNewLineDelimitedConcurrentFs2Stream[F[_]: ConcurrentEffect, A, CT <: String](implicit + A: Encode.Aux[A, CT] ) extends EncodeConcurrentFs2Stream[F, A, CT] { protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs).concat(newLine(cs)) } - protected final class EncodeNewLineDelimitedEffectFs2Stream[F[_]: Effect, A, CT <: String](implicit - A: Encode.Aux[A, CT] + final protected class EncodeNewLineDelimitedEffectFs2Stream[F[_]: Effect, A, CT <: String](implicit + A: Encode.Aux[A, CT] ) extends EncodeEffectFs2Stream[F, A, CT] { protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs).concat(newLine(cs)) } - protected abstract class EncodeConcurrentFs2Stream[F[_], A, CT <: String](implicit - F: Concurrent[F], - TA: ToAsync[Future, F] + abstract protected class EncodeConcurrentFs2Stream[F[_], A, CT <: String](implicit + F: Concurrent[F], + TA: ToAsync[Future, F] ) extends EncodeFs2Stream[F, A, CT] { protected def dispatch(reader: Reader[Buf], run: F[Unit]): F[Reader[Buf]] = F.bracketCase(F.start(run))(_ => F.pure(reader)) { case (f, ExitCase.Canceled) => f.cancel - case _ => F.unit + case _ => F.unit } } - protected abstract class EncodeEffectFs2Stream[F[_], A, CT <: String](implicit - F: Effect[F], - TA: ToAsync[Future, F] - ) extends EncodeFs2Stream[F, A, CT] with (Either[Throwable, Unit] => IO[Unit]) { + abstract protected class EncodeEffectFs2Stream[F[_], A, CT <: String](implicit + F: Effect[F], + TA: ToAsync[Future, F] + ) extends EncodeFs2Stream[F, A, CT] + with (Either[Throwable, Unit] => IO[Unit]) { def apply(cb: Either[Throwable, Unit]): IO[Unit] = IO.unit @@ -113,9 +110,9 @@ trait StreamInstances { F.productR(F.runAsync(run)(this).to[F])(F.pure(reader)) } - protected abstract class EncodeFs2Stream[F[_], A, CT <: String](implicit - F: Sync[F], - TA: ToAsync[Future, F] + abstract protected class EncodeFs2Stream[F[_], A, CT <: String](implicit + F: Sync[F], + TA: ToAsync[Future, F] ) extends EncodeStream[F, Stream, A] { type ContentType = CT @@ -126,12 +123,7 @@ trait StreamInstances { def apply(s: Stream[F, A], cs: Charset): F[Reader[Buf]] = F.suspend { val p = new Pipe[Buf] - val run = s - .map(chunk => encodeChunk(chunk, cs)) - .evalMap(chunk => TA(p.write(chunk))) - .onFinalize(F.suspend(TA(p.close()))) - .compile - .drain + val run = s.map(chunk => encodeChunk(chunk, cs)).evalMap(chunk => TA(p.write(chunk))).onFinalize(F.suspend(TA(p.close()))).compile.drain dispatch(p, run) } diff --git a/fs2/src/test/scala/io/finch/fs2/Fs2StreamingSpec.scala b/fs2/src/test/scala/io/finch/fs2/Fs2StreamingSpec.scala index f51c84be4..79f3982dc 100644 --- a/fs2/src/test/scala/io/finch/fs2/Fs2StreamingSpec.scala +++ b/fs2/src/test/scala/io/finch/fs2/Fs2StreamingSpec.scala @@ -12,14 +12,20 @@ class Fs2StreamingSpec extends FinchSpec with ScalaCheckDrivenPropertyChecks { checkConcurrentEffect[IO] def checkEffect[F[_]](implicit F: Effect[F]): Unit = - checkAll("fs2.streamBody[F[_]: Effect]", StreamingLaws[Stream, F]( - list => Stream(list:_*), - stream => F.toIO(stream.map(array => Buf.ByteArray.Owned(array)).compile.toList).unsafeRunSync() - ).all) + checkAll( + "fs2.streamBody[F[_]: Effect]", + StreamingLaws[Stream, F]( + list => Stream(list: _*), + stream => F.toIO(stream.map(array => Buf.ByteArray.Owned(array)).compile.toList).unsafeRunSync() + ).all + ) def checkConcurrentEffect[F[_]](implicit F: ConcurrentEffect[F]): Unit = - checkAll("fs2.streamBody[F[_]: ConcurrentEffect]", StreamingLaws[Stream, F]( - list => Stream(list:_*), - stream => F.toIO(stream.map(array => Buf.ByteArray.Owned(array)).compile.toList).unsafeRunSync() - ).all) + checkAll( + "fs2.streamBody[F[_]: ConcurrentEffect]", + StreamingLaws[Stream, F]( + list => Stream(list: _*), + stream => F.toIO(stream.map(array => Buf.ByteArray.Owned(array)).compile.toList).unsafeRunSync() + ).all + ) } diff --git a/generic/src/main/scala/io/finch/generic/FromParams.scala b/generic/src/main/scala/io/finch/generic/FromParams.scala index 0f304a407..d72c6d6a9 100644 --- a/generic/src/main/scala/io/finch/generic/FromParams.scala +++ b/generic/src/main/scala/io/finch/generic/FromParams.scala @@ -1,63 +1,63 @@ package io.finch.generic +import scala.reflect.ClassTag + import cats.data.NonEmptyList import cats.effect.Effect import io.finch._ -import scala.reflect.ClassTag import shapeless._ import shapeless.labelled._ import shapeless.poly._ /** - * A type class empowering a generic derivation of [[Endpoint]]s from query string params. - */ + * A type class empowering a generic derivation of [[Endpoint]]s from query string params. + */ trait FromParams[F[_], L <: HList] { def endpoint: Endpoint[F, L] } object FromParams { - implicit def hnilFromParams[F[_] : Effect]: FromParams[F, HNil] = new FromParams[F, HNil] { + implicit def hnilFromParams[F[_]: Effect]: FromParams[F, HNil] = new FromParams[F, HNil] { def endpoint: Endpoint[F, HNil] = Endpoint[F].const(HNil) } - implicit def hconsFromParams[F[_] : Effect, HK <: Symbol, HV, T <: HList](implicit - key: Witness.Aux[HK], - fpt: FromParams[F, T], - hс: Case1.Aux[Extractor.type, String, Endpoint[F, HV]] + implicit def hconsFromParams[F[_]: Effect, HK <: Symbol, HV, T <: HList](implicit + key: Witness.Aux[HK], + fpt: FromParams[F, T], + hс: Case1.Aux[Extractor.type, String, Endpoint[F, HV]] ): FromParams[F, FieldType[HK, HV] :: T] = new FromParams[F, FieldType[HK, HV] :: T] { - def endpoint: Endpoint[F, FieldType[HK, HV] :: T] = { + def endpoint: Endpoint[F, FieldType[HK, HV] :: T] = hс(key.value.name).map(field[HK](_)) :: fpt.endpoint - } } } private[generic] object Extractor extends Poly1 { - implicit def optionalExtractor[F[_] : Effect, V](implicit - dh: DecodeEntity[V], - ct: ClassTag[V] + implicit def optionalExtractor[F[_]: Effect, V](implicit + dh: DecodeEntity[V], + ct: ClassTag[V] ): Case.Aux[String, Endpoint[F, Option[V]]] = at[String] { key => Endpoint[F].paramOption[V](key) } - implicit def seqExtractor[F[_] : Effect, V](implicit - dh: DecodeEntity[V], - ct: ClassTag[V] + implicit def seqExtractor[F[_]: Effect, V](implicit + dh: DecodeEntity[V], + ct: ClassTag[V] ): Case.Aux[String, Endpoint[F, List[V]]] = at[String] { key => Endpoint[F].params[V](key) } - implicit def nelExtractor[F[_] : Effect, V](implicit - dh: DecodeEntity[V], - ct: ClassTag[V] + implicit def nelExtractor[F[_]: Effect, V](implicit + dh: DecodeEntity[V], + ct: ClassTag[V] ): Case.Aux[String, Endpoint[F, NonEmptyList[V]]] = at[String] { key => Endpoint[F].paramsNel[V](key) } - implicit def extractor[F[_] : Effect, V](implicit - dh: DecodeEntity[V], - ct: ClassTag[V] + implicit def extractor[F[_]: Effect, V](implicit + dh: DecodeEntity[V], + ct: ClassTag[V] ): Case.Aux[String, Endpoint[F, V]] = at[String] { key => Endpoint[F].param[V](key) } diff --git a/generic/src/main/scala/io/finch/generic/GenericDerivation.scala b/generic/src/main/scala/io/finch/generic/GenericDerivation.scala index ff6e46572..d8a90d6d9 100644 --- a/generic/src/main/scala/io/finch/generic/GenericDerivation.scala +++ b/generic/src/main/scala/io/finch/generic/GenericDerivation.scala @@ -4,9 +4,9 @@ import cats.effect.Effect import io.finch._ import shapeless._ -final class GenericDerivation[F[_] : Effect, A] { +final class GenericDerivation[F[_]: Effect, A] { def fromParams[Repr <: HList](implicit - gen: LabelledGeneric.Aux[A, Repr], - fp: FromParams[F, Repr] + gen: LabelledGeneric.Aux[A, Repr], + fp: FromParams[F, Repr] ): Endpoint[F, A] = fp.endpoint.map(gen.from) } diff --git a/generic/src/main/scala/io/finch/generic/package.scala b/generic/src/main/scala/io/finch/generic/package.scala index 536d2ab7d..5d676c9bc 100644 --- a/generic/src/main/scala/io/finch/generic/package.scala +++ b/generic/src/main/scala/io/finch/generic/package.scala @@ -3,8 +3,9 @@ package io.finch import cats.effect.Effect package object generic { + /** - * Generically derive a very basic instance of [[Endpoint]] for a given type `A`. - */ - def deriveEndpoint[F[_] : Effect, A]: GenericDerivation[F, A] = new GenericDerivation[F, A] + * Generically derive a very basic instance of [[Endpoint]] for a given type `A`. + */ + def deriveEndpoint[F[_]: Effect, A]: GenericDerivation[F, A] = new GenericDerivation[F, A] } diff --git a/generic/src/test/scala/io/finch/generic/DerivedEndpointLaws.scala b/generic/src/test/scala/io/finch/generic/DerivedEndpointLaws.scala index 64b9c7c79..fedca5d81 100644 --- a/generic/src/test/scala/io/finch/generic/DerivedEndpointLaws.scala +++ b/generic/src/test/scala/io/finch/generic/DerivedEndpointLaws.scala @@ -9,7 +9,7 @@ import io.finch._ import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws -abstract class DerivedEndpointLaws[F[_] : Effect, A] extends Laws with MissingInstances with AllInstances { +abstract class DerivedEndpointLaws[F[_]: Effect, A] extends Laws with MissingInstances with AllInstances { def endpoint: Endpoint[F, A] def toParams: A => Seq[(String, String)] @@ -23,14 +23,14 @@ abstract class DerivedEndpointLaws[F[_] : Effect, A] extends Laws with MissingIn new DefaultRuleSet( name = "evaluating", parent = None, - "roundTrip" -> Prop.forAll { (a: A) => roundTrip(a) } + "roundTrip" -> Prop.forAll((a: A) => roundTrip(a)) ) } object DerivedEndpointLaws { - def apply[F[_] : Effect, A]( - e: Endpoint[F, A], - tp: A => Seq[(String, String)] + def apply[F[_]: Effect, A]( + e: Endpoint[F, A], + tp: A => Seq[(String, String)] ): DerivedEndpointLaws[F, A] = new DerivedEndpointLaws[F, A] { val endpoint: Endpoint[F, A] = e val toParams = tp diff --git a/generic/src/test/scala/io/finch/generic/GenericSpec.scala b/generic/src/test/scala/io/finch/generic/GenericSpec.scala index 113781231..cfc356151 100644 --- a/generic/src/test/scala/io/finch/generic/GenericSpec.scala +++ b/generic/src/test/scala/io/finch/generic/GenericSpec.scala @@ -20,10 +20,11 @@ class GenericSpec extends FinchSpec { i <- Arbitrary.arbitrary[Int] } yield Foo(s, i)) - val f: Foo => Seq[(String, String)] = foo => Seq( - ("a" -> foo.a), - ("b" -> foo.b.toString) - ) + val f: Foo => Seq[(String, String)] = foo => + Seq( + ("a" -> foo.a), + ("b" -> foo.b.toString) + ) checkAll("DerivedEndpoint[Foo]", DerivedEndpointLaws[IO, Foo](e, f).evaluating) } diff --git a/iteratee/src/main/scala/io/finch/iteratee/package.scala b/iteratee/src/main/scala/io/finch/iteratee/package.scala index c675edb4f..063f84126 100644 --- a/iteratee/src/main/scala/io/finch/iteratee/package.scala +++ b/iteratee/src/main/scala/io/finch/iteratee/package.scala @@ -1,48 +1,46 @@ package io.finch +import java.nio.charset.Charset + import cats.effect.{Async, Effect, IO} import com.twitter.io._ import com.twitter.util.Future import io.finch.internal._ import io.iteratee.{Enumerator, Iteratee} -import java.nio.charset.Charset package object iteratee extends IterateeInstances { implicit def enumeratorLiftReader[F[_]](implicit - F: Async[F], - TE: ToAsync[Future, F] + F: Async[F], + TE: ToAsync[Future, F] ): LiftReader[Enumerator, F] = new LiftReader[Enumerator, F] { final def apply[A](reader: Reader[Buf], process: Buf => A): Enumerator[F, A] = { - def loop(): Enumerator[F, A] = { - Enumerator - .liftM[F, Option[Buf]](F.suspend(TE(reader.read()))) - .flatMap { - case None => Enumerator.empty[F, A] - case Some(buf) => Enumerator.enumOne[F, A](process(buf)).append(loop()) - } - } + def loop(): Enumerator[F, A] = + Enumerator.liftM[F, Option[Buf]](F.suspend(TE(reader.read()))).flatMap { + case None => Enumerator.empty[F, A] + case Some(buf) => Enumerator.enumOne[F, A](process(buf)).append(loop()) + } loop().ensure(F.delay(reader.discard())) } } implicit def encodeJsonEnumerator[F[_]: Effect, A](implicit - A: Encode.Json[A], - TE: ToAsync[Future, F] + A: Encode.Json[A], + TE: ToAsync[Future, F] ): EncodeStream.Json[F, Enumerator, A] = new EncodeNewLineDelimitedEnumerator[F, A, Application.Json] implicit def encodeSseEnumerator[F[_]: Effect, A](implicit - A: Encode.Aux[A, Text.EventStream], - TE: ToAsync[Future, F] + A: Encode.Aux[A, Text.EventStream], + TE: ToAsync[Future, F] ): EncodeStream.Aux[F, Enumerator, A, Text.EventStream] = new EncodeNewLineDelimitedEnumerator[F, A, Text.EventStream] implicit def encodeTextEnumerator[F[_]: Effect, A](implicit - A: Encode.Text[A], - TE: ToAsync[Future, F] + A: Encode.Text[A], + TE: ToAsync[Future, F] ): EncodeStream.Text[F, Enumerator, A] = new EncodeEnumerator[F, A, Text.Plain] { override protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs) @@ -52,18 +50,19 @@ package object iteratee extends IterateeInstances { trait IterateeInstances { - protected final class EncodeNewLineDelimitedEnumerator[F[_]: Effect, A, CT <: String](implicit - A: Encode.Aux[A, CT], - TE: ToAsync[Future, F] + final protected class EncodeNewLineDelimitedEnumerator[F[_]: Effect, A, CT <: String](implicit + A: Encode.Aux[A, CT], + TE: ToAsync[Future, F] ) extends EncodeEnumerator[F, A, CT] { protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs).concat(newLine(cs)) } - protected abstract class EncodeEnumerator[F[_], A, CT <: String](implicit - F: Effect[F], - TE: ToAsync[Future, F] - ) extends EncodeStream[F, Enumerator, A] with (Either[Throwable, Unit] => IO[Unit]) { + abstract protected class EncodeEnumerator[F[_], A, CT <: String](implicit + F: Effect[F], + TE: ToAsync[Future, F] + ) extends EncodeStream[F, Enumerator, A] + with (Either[Throwable, Unit] => IO[Unit]) { type ContentType = CT @@ -76,10 +75,7 @@ trait IterateeInstances { def apply(s: Enumerator[F, A], cs: Charset): F[Reader[Buf]] = { val p = new Pipe[Buf] - val run = s - .ensure(F.suspend(TE(p.close()))) - .map(chunk => encodeChunk(chunk, cs)) - .into(writeIteratee(p)) + val run = s.ensure(F.suspend(TE(p.close()))).map(chunk => encodeChunk(chunk, cs)).into(writeIteratee(p)) F.productR(F.runAsync(run)(this).to[F])(F.pure(p)) } diff --git a/iteratee/src/test/scala/io/finch/iteratee/IterateeStreamingSpec.scala b/iteratee/src/test/scala/io/finch/iteratee/IterateeStreamingSpec.scala index 3b4d13b29..53ca3f0c9 100644 --- a/iteratee/src/test/scala/io/finch/iteratee/IterateeStreamingSpec.scala +++ b/iteratee/src/test/scala/io/finch/iteratee/IterateeStreamingSpec.scala @@ -8,9 +8,12 @@ import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks class IterateeStreamingSpec extends FinchSpec with ScalaCheckDrivenPropertyChecks { - checkAll("Iteratee.streamBody", StreamingLaws[Enumerator, IO]( - Enumerator.enumList, - _.map(array => Buf.ByteArray.Owned(array)).toVector.unsafeRunSync().toList - ).all) + checkAll( + "Iteratee.streamBody", + StreamingLaws[Enumerator, IO]( + Enumerator.enumList, + _.map(array => Buf.ByteArray.Owned(array)).toVector.unsafeRunSync().toList + ).all + ) } diff --git a/json-test/src/main/scala/io/finch/test/AbstractJsonSpec.scala b/json-test/src/main/scala/io/finch/test/AbstractJsonSpec.scala index 9c66b1efd..60c0430aa 100644 --- a/json-test/src/main/scala/io/finch/test/AbstractJsonSpec.scala +++ b/json-test/src/main/scala/io/finch/test/AbstractJsonSpec.scala @@ -2,17 +2,18 @@ package io.finch.test import java.nio.charset.{Charset, StandardCharsets} -import cats.{Comonad, Eq, Functor} +import scala.util.Try + import cats.instances.AllInstances +import cats.{Comonad, Eq, Functor} import io.circe.Decoder -import io.finch.{Decode, DecodeStream, Encode} import io.finch.test.data._ +import io.finch.{Decode, DecodeStream, Encode} import org.scalacheck.{Arbitrary, Gen} -import org.scalatestplus.scalacheck.Checkers -import org.typelevel.discipline.Laws -import scala.util.Try import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.Checkers +import org.typelevel.discipline.Laws abstract class AbstractJsonSpec extends AnyFlatSpec with Matchers with Checkers with AllInstances { @@ -24,9 +25,13 @@ abstract class AbstractJsonSpec extends AnyFlatSpec with Matchers with Checkers def map[A, B](fa: Try[A])(f: A => B): Try[B] = fa.map(f) } - implicit def arbitraryCharset: Arbitrary[Charset] = Arbitrary(Gen.oneOf( - StandardCharsets.UTF_8, StandardCharsets.UTF_16, Charset.forName("UTF-32") - )) + implicit def arbitraryCharset: Arbitrary[Charset] = Arbitrary( + Gen.oneOf( + StandardCharsets.UTF_8, + StandardCharsets.UTF_16, + Charset.forName("UTF-32") + ) + ) implicit def arbitraryException: Arbitrary[Exception] = Arbitrary( Arbitrary.arbitrary[String].map(s => new Exception(s)) @@ -34,28 +39,26 @@ abstract class AbstractJsonSpec extends AnyFlatSpec with Matchers with Checkers implicit def eqException: Eq[Exception] = Eq.instance((a, b) => a.getMessage == b.getMessage) - implicit def decodeException: Decoder[Exception] = Decoder.forProduct1[Exception, String]("message")(s => - new Exception(s) - ) + implicit def decodeException: Decoder[Exception] = Decoder.forProduct1[Exception, String]("message")(s => new Exception(s)) private def loop(name: String, ruleSet: Laws#RuleSet, library: String): Unit = - for ((id, prop) <- ruleSet.all.properties) it should (s"$library.$id.$name") in { check(prop) } + for ((id, prop) <- ruleSet.all.properties) it should s"$library.$id.$name" in check(prop) def checkJson(library: String)(implicit - e: Encode.Json[List[ExampleNestedCaseClass]], - d: Decode.Json[List[ExampleNestedCaseClass]] + e: Encode.Json[List[ExampleNestedCaseClass]], + d: Decode.Json[List[ExampleNestedCaseClass]] ): Unit = { loop("List[ExampleNestedCaseClass]", JsonLaws.encoding[List[ExampleNestedCaseClass]].all, library) loop("List[ExampleNestedCaseClass]", JsonLaws.decoding[List[ExampleNestedCaseClass]].all, library) } def checkStreamJson[S[_[_], _], F[_]](library: String)( - fromList: List[ExampleNestedCaseClass] => S[F, ExampleNestedCaseClass], - toList: S[F, ExampleNestedCaseClass] => List[ExampleNestedCaseClass] - )(implicit en: DecodeStream.Json[S, F, ExampleNestedCaseClass], functor: Functor[S[F, ?]]): Unit = { + fromList: List[ExampleNestedCaseClass] => S[F, ExampleNestedCaseClass], + toList: S[F, ExampleNestedCaseClass] => List[ExampleNestedCaseClass] + )(implicit en: DecodeStream.Json[S, F, ExampleNestedCaseClass], functor: Functor[S[F, ?]]): Unit = loop( "ExampleNestedCaseClass", - JsonLaws.streaming[S, F, ExampleNestedCaseClass](fromList, toList).all, library + JsonLaws.streaming[S, F, ExampleNestedCaseClass](fromList, toList).all, + library ) - } } diff --git a/json-test/src/main/scala/io/finch/test/JsonLaws.scala b/json-test/src/main/scala/io/finch/test/JsonLaws.scala index ae6477ab6..dcf13de15 100644 --- a/json-test/src/main/scala/io/finch/test/JsonLaws.scala +++ b/json-test/src/main/scala/io/finch/test/JsonLaws.scala @@ -2,16 +2,16 @@ package io.finch.test import java.nio.charset.Charset -import cats.{Eq, Functor} import cats.instances.AllInstances import cats.laws._ import cats.laws.discipline._ +import cats.{Eq, Functor} import com.twitter.io.Buf import com.twitter.util._ -import io.circe.{Decoder, Encoder} import io.circe.jawn -import io.finch.{DecodeStream, _} +import io.circe.{Decoder, Encoder} import io.finch.internal.HttpContent +import io.finch.{DecodeStream, _} import org.scalacheck.{Arbitrary, Prop} import org.typelevel.discipline.Laws @@ -27,22 +27,23 @@ trait DecodeJsonLaws[A] extends Laws with AllInstances { decode(Buf.ByteArray.Owned(s"NOT A JSON$s".getBytes(cs.name)), cs).isLeft def all(implicit - a: Arbitrary[A], - cs: Arbitrary[Charset], - e: Encoder[A], - d: Decoder[A], - eq: Eq[A] + a: Arbitrary[A], + cs: Arbitrary[Charset], + e: Encoder[A], + d: Decoder[A], + eq: Eq[A] ): RuleSet = new DefaultRuleSet( name = "decode", parent = None, - "success" -> Prop.forAll { (a: A, cs: Charset) => success(a, cs) }, - "failure" -> Prop.forAll { (s: String, cs: Charset) => failure(s, cs) } + "success" -> Prop.forAll((a: A, cs: Charset) => success(a, cs)), + "failure" -> Prop.forAll((s: String, cs: Charset) => failure(s, cs)) ) } abstract class StreamJsonLaws[S[_[_], _], F[_], A](implicit - F: Functor[S[F, ?]] -) extends Laws with AllInstances { + F: Functor[S[F, ?]] +) extends Laws + with AllInstances { def streamDecoder: DecodeStream.Json[S, F, A] @@ -57,7 +58,7 @@ abstract class StreamJsonLaws[S[_[_], _], F[_], A](implicit } def failure(a: A, cs: Charset)(implicit e: Encoder[A]): Boolean = { - val json = F.map(fromList(a :: Nil))(a => e(a).noSpaces+"INVALID_JSON") + val json = F.map(fromList(a :: Nil))(a => e(a).noSpaces + "INVALID_JSON") val enum = F.map(json)(str => Buf.ByteArray.Owned(str.getBytes(cs.name))) Try( toList(streamDecoder(enum, cs)) @@ -65,15 +66,15 @@ abstract class StreamJsonLaws[S[_[_], _], F[_], A](implicit } def all(implicit - a: Arbitrary[A], - cs: Arbitrary[Charset], - encode: Encoder[A], - eq: Eq[A] - ): RuleSet = new DefaultRuleSet( + a: Arbitrary[A], + cs: Arbitrary[Charset], + encode: Encoder[A], + eq: Eq[A] + ): RuleSet = new DefaultRuleSet( name = "enumerate", parent = None, - "success" -> Prop.forAll { (a: List[A], cs: Charset) => success(a, cs) }, - "failure" -> Prop.forAll { (a: A, cs: Charset) => failure(a, cs) } + "success" -> Prop.forAll((a: List[A], cs: Charset) => success(a, cs)), + "failure" -> Prop.forAll((a: A, cs: Charset) => failure(a, cs)) ) } @@ -103,11 +104,11 @@ object JsonLaws { } def streaming[S[_[_], _], F[_], A]( - streamFromList: List[A] => S[F, A], - streamToList: S[F, A] => List[A] + streamFromList: List[A] => S[F, A], + streamToList: S[F, A] => List[A] )(implicit - decoder: DecodeStream.Json[S, F, A], - functor: Functor[S[F, ?]] + decoder: DecodeStream.Json[S, F, A], + functor: Functor[S[F, ?]] ): StreamJsonLaws[S, F, A] = new StreamJsonLaws[S, F, A] { val toList: S[F, A] => List[A] = streamToList diff --git a/json-test/src/main/scala/io/finch/test/data/ExampleNestedCaseClass.scala b/json-test/src/main/scala/io/finch/test/data/ExampleNestedCaseClass.scala index 844989545..35bd8cffa 100644 --- a/json-test/src/main/scala/io/finch/test/data/ExampleNestedCaseClass.scala +++ b/json-test/src/main/scala/io/finch/test/data/ExampleNestedCaseClass.scala @@ -5,7 +5,11 @@ import io.circe.{Decoder, Encoder} import org.scalacheck.{Arbitrary, Gen} case class ExampleNestedCaseClass( - string: String, double: Double, long: Long, ints: List[Int], example: ExampleCaseClass + string: String, + double: Double, + long: Long, + ints: List[Int], + example: ExampleCaseClass ) object ExampleNestedCaseClass { @@ -23,10 +27,13 @@ object ExampleNestedCaseClass { implicit val encoder: Encoder[ExampleNestedCaseClass] = Encoder.forProduct5[ExampleNestedCaseClass, String, Double, Long, List[Int], ExampleCaseClass]( - "string", "double", "long", "ints", "example" + "string", + "double", + "long", + "ints", + "example" )(e => (e.string, e.double, e.long, e.ints, e.example)) implicit val decoder: Decoder[ExampleNestedCaseClass] = Decoder.forProduct5("string", "double", "long", "ints", "example")(ExampleNestedCaseClass.apply) } - diff --git a/project/plugins.sbt b/project/plugins.sbt index 2cd60b751..fa300ed2f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,6 +11,8 @@ addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2-1") addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.4") -addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.0") addSbtPlugin("com.47deg" % "sbt-microsites" % "1.2.1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") +addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.10") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.21") diff --git a/refined/src/main/scala/io/finch/refined/PredicateFailed.scala b/refined/src/main/scala/io/finch/refined/PredicateFailed.scala index 337f27983..bc572e979 100644 --- a/refined/src/main/scala/io/finch/refined/PredicateFailed.scala +++ b/refined/src/main/scala/io/finch/refined/PredicateFailed.scala @@ -4,6 +4,6 @@ import scala.util.control.NoStackTrace case class PredicateFailed(error: String) extends Exception with NoStackTrace { - override def getMessage: String = error + override def getMessage: String = error } diff --git a/refined/src/main/scala/io/finch/refined/package.scala b/refined/src/main/scala/io/finch/refined/package.scala index ab999e67b..3b33bb95e 100644 --- a/refined/src/main/scala/io/finch/refined/package.scala +++ b/refined/src/main/scala/io/finch/refined/package.scala @@ -5,26 +5,25 @@ import eu.timepit.refined.api.{RefType, Validate} package object refined { implicit def decodePathRefined[F[_, _], A, B](implicit - ad: DecodePath[A], - v: Validate[A, B], - rt: RefType[F] + ad: DecodePath[A], + v: Validate[A, B], + rt: RefType[F] ): DecodePath[F[A, B]] = DecodePath.instance(s => ad(s).flatMap(p => rt.refine[B](p).right.toOption)) implicit def decodeEntityRefined[F[_, _], A, B](implicit - ad: DecodeEntity[A], - v: Validate[A, B], - rt: RefType[F] - ): DecodeEntity[F[A, B]] = { + ad: DecodeEntity[A], + v: Validate[A, B], + rt: RefType[F] + ): DecodeEntity[F[A, B]] = DecodeEntity.instance { s => ad(s) match { case Right(r) => rt.refine[B](r) match { case Left(error) => Left(PredicateFailed(error)) - case Right(ref) => Right(ref) + case Right(ref) => Right(ref) } case Left(e) => Left(e) } } - } } diff --git a/refined/src/test/scala/io/finch/refined/DecodeEntityRefinedSpec.scala b/refined/src/test/scala/io/finch/refined/DecodeEntityRefinedSpec.scala index 1d1357a1d..bb169a268 100644 --- a/refined/src/test/scala/io/finch/refined/DecodeEntityRefinedSpec.scala +++ b/refined/src/test/scala/io/finch/refined/DecodeEntityRefinedSpec.scala @@ -12,5 +12,4 @@ class DecodeEntityRefinedSpec extends FinchSpec { checkAll("DecodeEntity[Int Refined Positive]", DecodeEntityLaws[Int Refined Positive].all) checkAll("DecodeEntity[String Refined NonEmpty]", DecodeEntityLaws[String Refined NonEmpty].all) - } diff --git a/refined/src/test/scala/io/finch/refined/PredicateFailedSpec.scala b/refined/src/test/scala/io/finch/refined/PredicateFailedSpec.scala index 74c7574d5..97d1c5944 100644 --- a/refined/src/test/scala/io/finch/refined/PredicateFailedSpec.scala +++ b/refined/src/test/scala/io/finch/refined/PredicateFailedSpec.scala @@ -2,8 +2,8 @@ package io.finch.refined import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric._ -import io.finch._ import io.finch.FinchSpec +import io.finch._ class PredicateFailedSpec extends FinchSpec { diff --git a/scalastyle-config.xml b/scalastyle-config.xml deleted file mode 100644 index 6a632a4ff..000000000 --- a/scalastyle-config.xml +++ /dev/null @@ -1,85 +0,0 @@ - - Finch Configuration - - - FOR - IF - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - stdlib,all - (scala\.|java\.).+ - .+ - - - diff --git a/test/src/main/scala/io/finch/test/ServiceIntegrationSuite.scala b/test/src/main/scala/io/finch/test/ServiceIntegrationSuite.scala index 06b00ff04..10869b3e3 100644 --- a/test/src/main/scala/io/finch/test/ServiceIntegrationSuite.scala +++ b/test/src/main/scala/io/finch/test/ServiceIntegrationSuite.scala @@ -1,40 +1,37 @@ package io.finch.test -import com.twitter.finagle.{Http, ListeningServer, Service} import com.twitter.finagle.http.{Request, Response} +import com.twitter.finagle.{Http, ListeningServer, Service} import com.twitter.util.Await import org.scalatest.{FixtureTestSuite, Outcome} /** - * Extends [[ServiceSuite]] to support integration testing for services. - */ + * Extends [[ServiceSuite]] to support integration testing for services. + */ trait ServiceIntegrationSuite extends ServiceSuite { self: FixtureTestSuite => /** - * Override in implementing classes if a different port is desired for - * integration tests. - */ + * Override in implementing classes if a different port is desired for + * integration tests. + */ def port: Int = 8080 /** - * Provide a fixture containing a client that calls our locally-served - * service. - */ + * Provide a fixture containing a client that calls our locally-served + * service. + */ override def withFixture(test: OneArgTest): Outcome = { val service: Service[Request, Response] = createService() val server: ListeningServer = Http.serve(s":$port", service) val client: Service[Request, Response] = Http.newService(s"127.0.0.1:$port") - try { - self.withFixture(test.toNoArgTest(FixtureParam(client))) - } finally { - Await.ready( - for { - _ <- server.close() - _ <- client.close() - _ <- service.close() - } yield () - ) - } + try self.withFixture(test.toNoArgTest(FixtureParam(client))) + finally Await.ready( + for { + _ <- server.close() + _ <- client.close() + _ <- service.close() + } yield () + ) } } diff --git a/test/src/main/scala/io/finch/test/ServiceSuite.scala b/test/src/main/scala/io/finch/test/ServiceSuite.scala index 6374aca43..70e9da579 100644 --- a/test/src/main/scala/io/finch/test/ServiceSuite.scala +++ b/test/src/main/scala/io/finch/test/ServiceSuite.scala @@ -6,41 +6,38 @@ import com.twitter.util.{Await, Duration} import org.scalatest.{FixtureTestSuite, Outcome} /** - * A convenience class that is designed to make it easier to test HTTP services - * both directly and in integration tests that are served locally. Implementing - * classes must extend [[org.scalatest.FixtureTestSuite]] through [[org.scalatest.flatspec.FixtureAnyFlatSpec]] - * for example. - */ + * A convenience class that is designed to make it easier to test HTTP services + * both directly and in integration tests that are served locally. Implementing + * classes must extend [[org.scalatest.FixtureTestSuite]] through [[org.scalatest.flatspec.FixtureAnyFlatSpec]] + * for example. + */ trait ServiceSuite { self: FixtureTestSuite => /** - * Create an instance of the service to be tested. - */ + * Create an instance of the service to be tested. + */ def createService(): Service[Request, Response] /** - * The fixture type. Tests in this suite should take a [[FixtureParam]] - * argument. - */ + * The fixture type. Tests in this suite should take a [[FixtureParam]] + * argument. + */ case class FixtureParam(service: Service[Request, Response]) { /** - * Apply the service and await the response. - */ + * Apply the service and await the response. + */ def apply(req: Request, timeout: Duration = Duration.fromSeconds(10)): Response = Await.result(service(req), timeout) } /** - * By default we call the service directly, without serving it. - */ + * By default we call the service directly, without serving it. + */ def withFixture(test: OneArgTest): Outcome = { val service = createService() - try { - self.withFixture(test.toNoArgTest(FixtureParam(service))) - } finally { - Await.ready(service.close()) - } + try self.withFixture(test.toNoArgTest(FixtureParam(service))) + finally Await.ready(service.close()) } }