From 21b143b44c8959b360b772cf8065560c82111a60 Mon Sep 17 00:00:00 2001 From: Sergey Kolbasov Date: Tue, 26 Feb 2019 13:25:52 +0200 Subject: [PATCH] Least powerful type classes + Endpoint.mapF (#1089) * Least powerful type classes + Endpoint.mapF * Fix compilation * Add mapF scaladoc * Rename mapF to transformF --- core/src/main/scala/io/finch/Endpoint.scala | 100 +++++++++++------- .../main/scala/io/finch/EndpointModule.scala | 80 +++++++------- .../main/scala/io/finch/endpoint/body.scala | 18 ++-- .../main/scala/io/finch/endpoint/cookie.scala | 4 +- .../scala/io/finch/endpoint/endpoint.scala | 7 +- .../main/scala/io/finch/endpoint/header.scala | 4 +- .../scala/io/finch/endpoint/multipart.scala | 12 +-- .../main/scala/io/finch/endpoint/param.scala | 6 +- .../main/scala/io/finch/endpoint/path.scala | 8 +- .../main/scala/io/finch/internal/Mapper.scala | 26 ++--- .../{ToEffect.scala => ToAsync.scala} | 16 +-- core/src/main/scala/io/finch/package.scala | 2 +- .../test/scala/io/finch/EndpointSpec.scala | 9 ++ core/src/test/scala/io/finch/MethodSpec.scala | 2 +- .../io/finch/internal/ToEffectLaws.scala | 6 +- .../main/scala/io/finch/iteratee/Main.scala | 2 +- fs2/src/main/scala/io/finch/fs2/package.scala | 4 +- .../scala/io/finch/iteratee/package.scala | 16 +-- 18 files changed, 175 insertions(+), 147 deletions(-) rename core/src/main/scala/io/finch/internal/{ToEffect.scala => ToAsync.scala} (58%) diff --git a/core/src/main/scala/io/finch/Endpoint.scala b/core/src/main/scala/io/finch/Endpoint.scala index 1f8729e7e..9779c4e28 100644 --- a/core/src/main/scala/io/finch/Endpoint.scala +++ b/core/src/main/scala/io/finch/Endpoint.scala @@ -119,7 +119,7 @@ trait Endpoint[F[_], A] { self => } /** - * Transforms this endpoint to the given function `Future[Output[A]] => Future[Output[B]]`. + * 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 @@ -145,6 +145,24 @@ trait Endpoint[F[_], A] { self => final override def toString: String = self.toString } + /** + * Transform this endpoint to the given function `F[A] => F[B]` + */ + final def transformF[B](fn: F[A] => F[B])(implicit F: Monad[F]): Endpoint[F, B] = + new Endpoint[F, B] { + 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))) + }) + case skipped: EndpointResult.NotMatched[F] => skipped + } + + final override def item = self.item + final override def toString: String = self.toString + } + /** * Returns a product of this and `other` endpoint. The resulting endpoint returns a tuple * of both values. @@ -497,7 +515,7 @@ object Endpoint { self.map(value => gen.from(value :: HNil)) } - implicit def endpointInstances[F[_] : Effect]: Alternative[({type T[B] = Endpoint[F, B]})#T] = { + implicit def endpointInstances[F[_] : Sync]: Alternative[({type T[B] = Endpoint[F, B]})#T] = { new Alternative[({type T[B] = Endpoint[F, B]})#T] { final override def ap[A, B](ff: Endpoint[F, A => B])(fa: Endpoint[F, A]): Endpoint[F, B] = @@ -617,7 +635,7 @@ object Endpoint { * @see [[fromFile]] */ def fromInputStream[F[_]](stream: Resource[F, InputStream])( - implicit F: Effect[F], S: ContextShift[F] + implicit F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = new FromInputStream[F](stream) /** @@ -627,7 +645,7 @@ object Endpoint { * @see [[fromInputStream]] */ def fromFile[F[_]](file: File)( - implicit F: Effect[F], S: ContextShift[F] + implicit F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = fromInputStream[F]( Resource.fromAutoCloseable(F.delay(new FileInputStream(file))) @@ -665,7 +683,7 @@ object Endpoint { * @see https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html */ def classpathAsset[F[_]](path: String)(implicit - F: Effect[F], + F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = { val asset = new Asset[F](path) @@ -691,7 +709,7 @@ object Endpoint { * }}} */ def filesystemAsset[F[_]](path: String)(implicit - F: Effect[F], + F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = { val asset = new Asset[F](path) @@ -703,7 +721,7 @@ object Endpoint { /** * A root [[Endpoint]] that always matches and extracts the current request. */ - def root[F[_]](implicit F: Effect[F]): Endpoint[F, Request] = + def root[F[_]](implicit F: Sync[F]): Endpoint[F, Request] = new Endpoint[F, Request] { final def apply(input: Input): Result[F, Request] = EndpointResult.Matched(input, Trace.empty, F.delay(Output.payload(input.request))) @@ -744,20 +762,20 @@ 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. */ - def path[F[_]: Effect, A: DecodePath: ClassTag]: Endpoint[F, A] = + 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. */ - def paths[F[_]: Effect, A: DecodePath: ClassTag]: Endpoint[F, List[A]] = + def paths[F[_]: Sync, A: DecodePath: ClassTag]: Endpoint[F, List[A]] = new ExtractPaths[F, A] /** * An [[Endpoint]] that matches a given string. */ - def path[F[_]: Effect](s: String): Endpoint[F, HNil] = + def path[F[_]: Sync](s: String): Endpoint[F, HNil] = new MatchPath[F](s) /** @@ -828,21 +846,21 @@ object Endpoint { * 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[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = + 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`. */ - def headerOption[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = + 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. */ - def binaryBodyOption[F[_]: Effect]: Endpoint[F, Option[Array[Byte]]] = + def binaryBodyOption[F[_]: Sync]: Endpoint[F, Option[Array[Byte]]] = new BinaryBody[F, Option[Array[Byte]]] with FullBody.Optional[F, Array[Byte]] /** @@ -850,14 +868,14 @@ object Endpoint { * `Array[Byte]`, or throws a [[Error.NotPresent]] exception. The returned [[Endpoint]] only * matches non-chunked (non-streamed) requests. */ - def binaryBody[F[_]: Effect]: Endpoint[F, Array[Byte]] = + 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. */ - def stringBodyOption[F[_]: Effect]: Endpoint[F, Option[String]] = + def stringBodyOption[F[_]: Sync]: Endpoint[F, Option[String]] = new StringBody[F, Option[String]] with FullBody.Optional[F, String] /** @@ -865,7 +883,7 @@ object Endpoint { * throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] only matches non-chunked * (non-streamed) requests. */ - def stringBody[F[_]: Effect]: Endpoint[F, String] = + def stringBody[F[_]: Sync]: Endpoint[F, String] = new StringBody[F, String] with FullBody.Required[F, String] /** @@ -873,7 +891,7 @@ object Endpoint { * interpreted as `A`, into an `Option`. The returned [[Endpoint]] only matches non-chunked * (non-streamed) requests. */ - def bodyOption[F[_]: Effect, A: ClassTag, CT](implicit D: Decode.Dispatchable[A, CT]): Endpoint[F, Option[A]] = + 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] /** @@ -881,31 +899,31 @@ object Endpoint { * interpreted as `A`, or throws an [[Error.NotPresent]] exception. The returned [[Endpoint]] * only matches non-chunked (non-streamed) requests. */ - def body[F[_]: Effect, A: ClassTag, CT](implicit d: Decode.Dispatchable[A, CT]): Endpoint[F, A] = + 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]`. */ - def jsonBody[F[_]: Effect, A: Decode.Json: ClassTag]: Endpoint[F, A] = + def jsonBody[F[_]: Sync, A: Decode.Json: ClassTag]: Endpoint[F, A] = body[F, A, Application.Json] /** * Alias for `bodyOption[F, A, Application.Json]`. */ - def jsonBodyOption[F[_]: Effect, A: Decode.Json: ClassTag]: Endpoint[F, Option[A]] = + def jsonBodyOption[F[_]: Sync, A: Decode.Json: ClassTag]: Endpoint[F, Option[A]] = bodyOption[F, A, Application.Json] /** * Alias for `body[F, A, Text.Plain]` */ - def textBody[F[_]: Effect, A: Decode.Text: ClassTag]: Endpoint[F, A] = + def textBody[F[_]: Sync, A: Decode.Text: ClassTag]: Endpoint[F, A] = body[F, A, Text.Plain] /** * Alias for `bodyOption[A, Text.Plain]` */ - def textBodyOption[F[_]: Effect, A: Decode.Text: ClassTag]: Endpoint[F, Option[A]] = + def textBodyOption[F[_]: Sync, A: Decode.Text: ClassTag]: Endpoint[F, Option[A]] = bodyOption[F, A, Text.Plain] /** @@ -920,7 +938,7 @@ object Endpoint { * bin: Endpoint[IO, Enumerator[IO, Array[Byte]]] = binaryBodyStream * }}} */ - def binaryBodyStream[F[_]: Effect, S[_[_], _]](implicit + def binaryBodyStream[F[_]: Sync, S[_[_], _]](implicit LR: LiftReader[S, F] ): Endpoint[F, S[F, Array[Byte]]] = new BinaryBodyStream[F, S] @@ -936,7 +954,7 @@ object Endpoint { * bin: Endpoint[IO, Enumerator[IO, String]] = stringBodyStream * }}} */ - def stringBodyStream[F[_]: Effect, S[_[_], _]](implicit + def stringBodyStream[F[_]: Sync, S[_[_], _]](implicit LR: LiftReader[S, F] ): Endpoint[F, S[F, String]] = new StringBodyStream[F, S] @@ -958,7 +976,7 @@ object Endpoint { * bin: Endpoint[IO, Enumerator[IO, Foo]] = bodyStream * }}} */ - def bodyStream[F[_]: Effect, S[_[_], _], A, CT <: String](implicit + def bodyStream[F[_]: Sync, S[_[_], _], A, CT <: String](implicit LR: LiftReader[S, F], A: DecodeStream.Aux[S, F, A, CT] ): Endpoint[F, S[F, A]] = new BodyStream[F, S, A, CT] @@ -966,7 +984,7 @@ object Endpoint { /** * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Application.Json]`. */ - def jsonBodyStream[F[_]: Effect, S[_[_], _], A](implicit + 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] @@ -974,7 +992,7 @@ object Endpoint { /** * See [[bodyStream]]. This is just an alias for `bodyStream[?, ?, Text.Plain]`. */ - def textBodyStream[F[_]: Effect, S[_[_], _], A](implicit + def textBodyStream[F[_]: Sync, S[_[_], _], A](implicit LR: LiftReader[S, F], A: DecodeStream.Aux[S, F, A, Text.Plain] ): Endpoint[F, S[F, A]] = bodyStream[F, S, A, Text.Plain] @@ -983,21 +1001,21 @@ object Endpoint { * An evaluating [[Endpoint]] that reads an optional HTTP cookie from the request into an * `Option`. */ - def cookieOption[F[_]: Effect](name: String): Endpoint[F, Option[FinagleCookie]] = + 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. */ - def cookie[F[_]: Effect](name: String): Endpoint[F, FinagleCookie] = + 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`. */ - def paramOption[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = + def paramOption[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = new Param[F, Option, A](name) with Param.Optional[F, A] /** @@ -1005,14 +1023,14 @@ object Endpoint { * request or raises an [[Error.NotPresent]] exception when the param is missing; an * [[Error.NotValid]] exception is the param is empty. */ - def param[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = + 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`. */ - def params[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, List[A]] = + def params[F[_]: Sync, A: DecodeEntity: ClassTag](name: String): Endpoint[F, List[A]] = new Params[F, List, A](name) with Params.AllowEmpty[F, A] /** @@ -1020,62 +1038,62 @@ object Endpoint { * from the request into a `NonEmptyList` or raises a [[Error.NotPresent]] exception * when the params are missing or empty. */ - def paramsNel[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, NonEmptyList[A]] = + 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`. */ - def multipartFileUploadOption[F[_]: Effect](name: String): Endpoint[F, Option[FinagleMultipart.FileUpload]] = + 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. */ - def multipartFileUpload[F[_]: Effect](name: String): Endpoint[F, FinagleMultipart.FileUpload] = + 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. */ - def multipartFileUploads[F[_]: Effect](name: String): Endpoint[F, List[FinagleMultipart.FileUpload]] = + 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. */ - def multipartFileUploadsNel[F[_]: Effect](name: String): Endpoint[F, NonEmptyList[FinagleMultipart.FileUpload]] = + 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. */ - def multipartAttribute[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, A] = + 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. */ - def multipartAttributeOption[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, Option[A]] = + 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. */ - def multipartAttributes[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, List[A]] = + 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. */ - def multipartAttributesNel[F[_]: Effect, A: DecodeEntity: ClassTag](name: String): Endpoint[F, NonEmptyList[A]] = + 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] } diff --git a/core/src/main/scala/io/finch/EndpointModule.scala b/core/src/main/scala/io/finch/EndpointModule.scala index 84c5d4201..68889b0de 100644 --- a/core/src/main/scala/io/finch/EndpointModule.scala +++ b/core/src/main/scala/io/finch/EndpointModule.scala @@ -2,7 +2,7 @@ package io.finch import cats.Applicative import cats.data.NonEmptyList -import cats.effect.{ContextShift, Effect, Resource, Sync} +import cats.effect.{ContextShift, Resource, Sync} import com.twitter.finagle.http.{Cookie, Request} import com.twitter.finagle.http.exp.Multipart import com.twitter.io.Buf @@ -99,7 +99,7 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.fromInputStream]]. */ def fromInputStream(stream: Resource[F, InputStream])( - implicit F: Effect[F], S: ContextShift[F] + implicit F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = Endpoint.fromInputStream[F](stream) @@ -107,26 +107,26 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.fromFile]]. */ def fromFile(file: File)( - implicit F: Effect[F], S: ContextShift[F] + implicit F: Sync[F], S: ContextShift[F] ): Endpoint[F, Buf] = Endpoint.fromFile[F](file) /** * An alias for [[Endpoint.classpathAsset]]. */ - def classpathAsset(path: String)(implicit F: Effect[F], S: ContextShift[F]): Endpoint[F, Buf] = + def classpathAsset(path: String)(implicit F: Sync[F], S: ContextShift[F]): Endpoint[F, Buf] = Endpoint.classpathAsset[F](path) /** * An alias for [[Endpoint.classpathAsset]]. */ - def filesystemAsset(path: String)(implicit F: Effect[F], S: ContextShift[F]): Endpoint[F, Buf] = + def filesystemAsset(path: String)(implicit F: Sync[F], S: ContextShift[F]): Endpoint[F, Buf] = Endpoint.filesystemAsset[F](path) /** * An alias for [[Endpoint.root]]. */ - def root(implicit F: Effect[F]): Endpoint[F, Request] = + def root(implicit F: Sync[F]): Endpoint[F, Request] = Endpoint.root[F] /** @@ -144,13 +144,13 @@ trait EndpointModule[F[_]] { /** * An alias for [[Endpoint.path]]. */ - def path[A: DecodePath: ClassTag](implicit F: Effect[F]): Endpoint[F, A] = + def path[A: DecodePath: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.path[F, A] /** * An alias for [[Endpoint.paths]]. */ - def paths[A: DecodePath: ClassTag](implicit F: Effect[F]): Endpoint[F, List[A]] = + def paths[A: DecodePath: ClassTag](implicit F: Sync[F]): Endpoint[F, List[A]] = Endpoint.paths[F, A] /** @@ -159,7 +159,7 @@ trait EndpointModule[F[_]] { * @note This method is implicit such that an implicit conversion `String => Endpoint[F, HNil]` * works. */ - implicit def path(s: String)(implicit F: Effect[F]): Endpoint[F, HNil] = + implicit def path(s: String)(implicit F: Sync[F]): Endpoint[F, HNil] = Endpoint.path[F](s) /** @@ -213,80 +213,80 @@ trait EndpointModule[F[_]] { /** * An alias for [[Endpoint.header]]. */ - def header[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, A] = + def header[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.header[F, A](name) /** * An alias for [[Endpoint.headerOption]]. */ - def headerOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, Option[A]] = + 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]]. */ - def binaryBodyOption(implicit F: Effect[F]): Endpoint[F, Option[Array[Byte]]] = + def binaryBodyOption(implicit F: Sync[F]): Endpoint[F, Option[Array[Byte]]] = Endpoint.binaryBodyOption[F] /** * An alias for [[Endpoint.binaryBody]]. */ - def binaryBody(implicit F: Effect[F]): Endpoint[F, Array[Byte]] = + def binaryBody(implicit F: Sync[F]): Endpoint[F, Array[Byte]] = Endpoint.binaryBody[F] /** * An alias for [[Endpoint.stringBodyOption]]. */ - def stringBodyOption(implicit F: Effect[F]): Endpoint[F, Option[String]] = + def stringBodyOption(implicit F: Sync[F]): Endpoint[F, Option[String]] = Endpoint.stringBodyOption[F] /** * An alias for [[Endpoint.stringBody]]. */ - def stringBody(implicit F: Effect[F]): Endpoint[F, String] = + def stringBody(implicit F: Sync[F]): Endpoint[F, String] = Endpoint.stringBody[F] /** * An alias for [[Endpoint.bodyOption]]. */ - def bodyOption[A: ClassTag, CT](implicit F: Effect[F], D: Decode.Dispatchable[A, CT]): Endpoint[F, Option[A]] = + 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]]. */ - def body[A: ClassTag, CT](implicit D: Decode.Dispatchable[A, CT], F: Effect[F]): Endpoint[F, A] = + 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]]. */ - def jsonBody[A: Decode.Json: ClassTag](implicit F: Effect[F]): Endpoint[F, A] = + def jsonBody[A: Decode.Json: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.jsonBody[F, A] /** * An alias for [[Endpoint.jsonBodyOption]]. */ - def jsonBodyOption[A: Decode.Json: ClassTag](implicit F: Effect[F]): Endpoint[F, Option[A]] = + def jsonBodyOption[A: Decode.Json: ClassTag](implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.jsonBodyOption[F, A] /** * An alias for [[Endpoint.textBody]]. */ - def textBody[A: Decode.Text: ClassTag](implicit F: Effect[F]): Endpoint[F, A] = + def textBody[A: Decode.Text: ClassTag](implicit F: Sync[F]): Endpoint[F, A] = Endpoint.textBody[F, A] /** * An alias for [[Endpoint.textBodyOption]]. */ - def textBodyOption[A: Decode.Text: ClassTag](implicit F: Effect[F]): Endpoint[F, Option[A]] = + def textBodyOption[A: Decode.Text: ClassTag](implicit F: Sync[F]): Endpoint[F, Option[A]] = Endpoint.textBodyOption[F, A] /** * An alias for [[Endpoint.binaryBodyStream]]. */ def binaryBodyStream[S[_[_], _]](implicit - F: Effect[F], + F: Sync[F], LR: LiftReader[S, F] ): Endpoint[F, S[F, Array[Byte]]] = Endpoint.binaryBodyStream[F, S] @@ -294,7 +294,7 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.stringBodyStream]]. */ def stringBodyStream[S[_[_], _]](implicit - F: Effect[F], + F: Sync[F], LR: LiftReader[S, F] ): Endpoint[F, S[F, String]] = Endpoint.stringBodyStream[F, S] @@ -302,7 +302,7 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.bodyStream]]. */ def bodyStream[S[_[_], _], A, CT <: String](implicit - F: Effect[F], + 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] @@ -311,7 +311,7 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.jsonBodyStream]]. */ def jsonBodyStream[S[_[_], _], A](implicit - F: Effect[F], + 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] @@ -320,7 +320,7 @@ trait EndpointModule[F[_]] { * An alias for [[Endpoint.textBodyStream]]. */ def textBodyStream[S[_[_], _], A](implicit - F: Effect[F], + 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] @@ -328,85 +328,85 @@ trait EndpointModule[F[_]] { /** * An alias for [[Endpoint.cookieOption]]. */ - def cookieOption(name: String)(implicit F: Effect[F]): Endpoint[F, Option[Cookie]] = + def cookieOption(name: String)(implicit F: Sync[F]): Endpoint[F, Option[Cookie]] = Endpoint.cookieOption[F](name) /** * An alias for [[Endpoint.cookie]]. */ - def cookie(name: String)(implicit F: Effect[F]): Endpoint[F, Cookie] = + def cookie(name: String)(implicit F: Sync[F]): Endpoint[F, Cookie] = Endpoint.cookie[F](name) /** * An alias for [[Endpoint.paramOption]]. */ - def paramOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, Option[A]] = + 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]]. */ - def param[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, A] = + def param[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.param[F, A](name) /** * An alias for [[Endpoint.params]]. */ - def params[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, List[A]] = + 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]]. */ - def paramsNel[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, NonEmptyList[A]] = + 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]]. */ - def multipartFileUploadOption(name: String)(implicit F: Effect[F]): Endpoint[F, Option[Multipart.FileUpload]] = + def multipartFileUploadOption(name: String)(implicit F: Sync[F]): Endpoint[F, Option[Multipart.FileUpload]] = Endpoint.multipartFileUploadOption[F](name) /** * An alias for [[Endpoint.multipartFileUpload]]. */ - def multipartFileUpload(name: String)(implicit F: Effect[F]): Endpoint[F, Multipart.FileUpload] = + def multipartFileUpload(name: String)(implicit F: Sync[F]): Endpoint[F, Multipart.FileUpload] = Endpoint.multipartFileUpload[F](name) /** * An alias for [[Endpoint.multipartFileUploads]]. */ - def multipartFileUploads(name: String)(implicit F: Effect[F]): Endpoint[F, List[Multipart.FileUpload]] = + def multipartFileUploads(name: String)(implicit F: Sync[F]): Endpoint[F, List[Multipart.FileUpload]] = Endpoint.multipartFileUploads[F](name) /** * An alias for [[Endpoint.multipartFileUploadsNel]]. */ - def multipartFileUploadsNel(name: String)(implicit F: Effect[F]): Endpoint[F, NonEmptyList[Multipart.FileUpload]] = + def multipartFileUploadsNel(name: String)(implicit F: Sync[F]): Endpoint[F, NonEmptyList[Multipart.FileUpload]] = Endpoint.multipartFileUploadsNel[F](name) /** * An alias for [[Endpoint.multipartAttribute]]. */ - def multipartAttribute[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, A] = + def multipartAttribute[A: DecodeEntity: ClassTag](name: String)(implicit F: Sync[F]): Endpoint[F, A] = Endpoint.multipartAttribute[F, A](name) /** * An alias for [[Endpoint.multipartAttributeOption]]. */ - def multipartAttributeOption[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, Option[A]] = + 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]]. */ - def multipartAttributes[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, List[A]] = + 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]]. */ - def multipartAttributesNel[A: DecodeEntity: ClassTag](name: String)(implicit F: Effect[F]): Endpoint[F, NonEmptyList[A]] = + 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/endpoint/body.scala b/core/src/main/scala/io/finch/endpoint/body.scala index cfac6a3d4..467bc7f0b 100644 --- a/core/src/main/scala/io/finch/endpoint/body.scala +++ b/core/src/main/scala/io/finch/endpoint/body.scala @@ -1,6 +1,6 @@ package io.finch.endpoint -import cats.effect.Effect +import cats.effect.Sync import com.twitter.io.{Buf, Reader} import io.finch._ import io.finch.internal._ @@ -10,7 +10,7 @@ import scala.reflect.ClassTag private[finch] abstract class FullBody[F[_], A] extends Endpoint[F, A] { - protected def F: Effect[F] + protected def F: Sync[F] protected def missing: F[Output[A]] protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]] @@ -53,7 +53,7 @@ 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: Effect[F] + 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]] = @@ -65,7 +65,7 @@ private[finch] abstract class Body[F[_], A, B, CT](implicit final override def toString: String = "body" } -private[finch] abstract class BinaryBody[F[_], A](implicit protected val F: Effect[F]) +private[finch] abstract 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]] = @@ -74,7 +74,7 @@ private[finch] abstract class BinaryBody[F[_], A](implicit protected val F: Effe final override def toString: String = "binaryBody" } -private[finch] abstract class StringBody[F[_], A](implicit protected val F: Effect[F]) +private[finch] abstract class StringBody[F[_], A](implicit protected val F: Sync[F]) extends FullBody[F, A] with FullBody.PreparedBody[F, String, A] { @@ -86,7 +86,7 @@ private[finch] abstract class StringBody[F[_], A](implicit protected val F: Effe private[finch] abstract class ChunkedBody[F[_], S[_[_], _], A] extends Endpoint[F, S[F, A]] { - protected def F: Effect[F] + 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]] = @@ -102,7 +102,7 @@ private[finch] abstract class ChunkedBody[F[_], S[_[_], _], A] extends Endpoint[ private[finch] final class BinaryBodyStream[F[_], S[_[_], _]](implicit LR: LiftReader[S, F], - protected val F: Effect[F] + protected val F: Sync[F] ) extends ChunkedBody[F, S, Array[Byte]] with (Buf => Array[Byte]) { def apply(buf: Buf): Array[Byte] = buf.asByteArray @@ -115,7 +115,7 @@ private[finch] final class BinaryBodyStream[F[_], S[_[_], _]](implicit private[finch] final class StringBodyStream[F[_], S[_[_], _]](implicit LR: LiftReader[S, F], - protected val F: Effect[F] + protected val F: Sync[F] ) extends ChunkedBody[F, S, String] with (Buf => String) { def apply(buf: Buf): String = buf.asString(StandardCharsets.UTF_8) @@ -129,7 +129,7 @@ private[finch] final class StringBodyStream[F[_], S[_[_], _]](implicit } private[finch] final class BodyStream[F[_], S[_[_], _], A, CT <: String](implicit - protected val F: Effect[F], + protected val F: Sync[F], LR: LiftReader[S, F], A: DecodeStream.Aux[S, F, A, CT] ) extends ChunkedBody[F, S, A] { diff --git a/core/src/main/scala/io/finch/endpoint/cookie.scala b/core/src/main/scala/io/finch/endpoint/cookie.scala index 11169f2ee..7be52cb3d 100644 --- a/core/src/main/scala/io/finch/endpoint/cookie.scala +++ b/core/src/main/scala/io/finch/endpoint/cookie.scala @@ -1,11 +1,11 @@ package io.finch.endpoint -import cats.effect.Effect +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: Effect[F] + protected val F: Sync[F] ) extends Endpoint[F, A] { protected def missing(name: String): F[Output[A]] diff --git a/core/src/main/scala/io/finch/endpoint/endpoint.scala b/core/src/main/scala/io/finch/endpoint/endpoint.scala index d3998dc15..09e5c756f 100644 --- a/core/src/main/scala/io/finch/endpoint/endpoint.scala +++ b/core/src/main/scala/io/finch/endpoint/endpoint.scala @@ -1,6 +1,7 @@ package io.finch -import cats.effect.{ContextShift, Effect, Resource} +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 @@ -10,7 +11,7 @@ import shapeless.HNil package object endpoint { private[finch] class FromInputStream[F[_]](stream: Resource[F, InputStream])( - implicit F: Effect[F], S: ContextShift[F] + implicit F: Sync[F], S: ContextShift[F] ) extends Endpoint[F, Buf] { private def readLoop(left: Buf, stream: InputStream): F[Buf] = F.suspend { @@ -30,7 +31,7 @@ package object endpoint { ) } - private[finch] class Asset[F[_]](path: String)(implicit F: Effect[F]) extends Endpoint[F, HNil] { + private[finch] class Asset[F[_]](path: String)(implicit F: Applicative[F]) extends Endpoint[F, HNil] { final def apply(input: Input): Endpoint.Result[F, HNil] = { val req = input.request if (req.method != FinagleMethod.Get || req.path != path) EndpointResult.NotMatched[F] diff --git a/core/src/main/scala/io/finch/endpoint/header.scala b/core/src/main/scala/io/finch/endpoint/header.scala index d7c7fc771..f0993ec51 100644 --- a/core/src/main/scala/io/finch/endpoint/header.scala +++ b/core/src/main/scala/io/finch/endpoint/header.scala @@ -1,7 +1,7 @@ package io.finch.endpoint import cats.Id -import cats.effect.Effect +import cats.effect.Sync import io.finch._ import io.finch.items._ import scala.reflect.ClassTag @@ -9,7 +9,7 @@ import scala.reflect.ClassTag private[finch] abstract class Header[F[_], G[_], A](name: String)(implicit d: DecodeEntity[A], tag: ClassTag[A], - protected val F: Effect[F] + protected val F: Sync[F] ) extends Endpoint[F, G[A]] { self => protected def missing(name: String): F[Output[G[A]]] diff --git a/core/src/main/scala/io/finch/endpoint/multipart.scala b/core/src/main/scala/io/finch/endpoint/multipart.scala index 9f7e351f9..3c12205a4 100644 --- a/core/src/main/scala/io/finch/endpoint/multipart.scala +++ b/core/src/main/scala/io/finch/endpoint/multipart.scala @@ -2,7 +2,7 @@ package io.finch.endpoint import cats.Id import cats.data.NonEmptyList -import cats.effect.Effect +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} @@ -11,12 +11,12 @@ import io.finch.items._ import scala.reflect.ClassTag import scala.util.control.NonFatal -private[finch] abstract class Attribute[F[_]: Effect, G[_], A](val name: String)(implicit +private[finch] abstract class Attribute[F[_]: Sync, G[_], A](val name: String)(implicit d: DecodeEntity[A], tag: ClassTag[A] ) extends Endpoint[F, G[A]] { - protected def F: Effect[F] = Effect[F] + protected def F: Sync[F] = Sync[F] protected def missing(name: String): F[Output[G[A]]] protected def present(value: NonEmptyList[A]): F[Output[G[A]]] protected def unparsed(errors: NonEmptyList[Throwable], tag: ClassTag[A]): F[Output[G[A]]] @@ -98,10 +98,10 @@ private[finch] object Attribute { } } -private[finch] abstract class FileUpload[F[_]: Effect, G[_]](name: String) +private[finch] abstract class FileUpload[F[_]: Sync, G[_]](name: String) extends Endpoint[F, G[FinagleMultipart.FileUpload]] { - protected def F: Effect[F] = Effect[F] + 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]]] @@ -115,7 +115,7 @@ private[finch] abstract class FileUpload[F[_]: Effect, G[_]](name: String) final def apply(input: Input): EndpointResult[F, G[FinagleFileUpload]] = if (input.request.isChunked) EndpointResult.NotMatched[F] else { - val output = Effect[F].suspend { + val output = Sync[F].suspend { all(input) match { case Some(nel) => present(nel) case None => missing(name) diff --git a/core/src/main/scala/io/finch/endpoint/param.scala b/core/src/main/scala/io/finch/endpoint/param.scala index 192ad6661..362bedc03 100644 --- a/core/src/main/scala/io/finch/endpoint/param.scala +++ b/core/src/main/scala/io/finch/endpoint/param.scala @@ -2,14 +2,14 @@ package io.finch.endpoint import cats.Id import cats.data.NonEmptyList -import cats.effect.Effect +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: Effect[F] + protected val F: Sync[F] ) extends Endpoint[F, G[A]] { self => protected def missing(name: String): F[Output[G[A]]] @@ -50,7 +50,7 @@ private[finch] object Param { private[finch] abstract class Params[F[_], G[_], A](name: String)(implicit d: DecodeEntity[A], tag: ClassTag[A], - protected val F: Effect[F] + 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 de665dc63..cb04b3eb1 100644 --- a/core/src/main/scala/io/finch/endpoint/path.scala +++ b/core/src/main/scala/io/finch/endpoint/path.scala @@ -1,13 +1,13 @@ package io.finch.endpoint -import cats.effect.Effect +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: Effect[F] + F: Applicative[F] ) extends Endpoint[F, HNil] { final def apply(input: Input): EndpointResult[F, HNil] = input.route match { case `s` :: rest => @@ -25,7 +25,7 @@ private[finch] class MatchPath[F[_]](s: String)(implicit private[finch] class ExtractPath[F[_], A](implicit d: DecodePath[A], ct: ClassTag[A], - F: Effect[F] + 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 { @@ -46,7 +46,7 @@ private[finch] class ExtractPath[F[_], A](implicit private[finch] class ExtractPaths[F[_], A](implicit d: DecodePath[A], ct: ClassTag[A], - F: Effect[F] + 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 46bd36ad4..375ce4b27 100644 --- a/core/src/main/scala/io/finch/internal/Mapper.scala +++ b/core/src/main/scala/io/finch/internal/Mapper.scala @@ -1,7 +1,7 @@ package io.finch.internal import cats.Monad -import cats.effect.Effect +import cats.effect.Async import cats.syntax.functor._ import com.twitter.finagle.http.Response import io.finch.{Endpoint, Output} @@ -76,35 +76,35 @@ private[finch] trait HighPriorityMapperConversions extends LowPriorityMapperConv implicit def mapperFromResponseValue[F[_] : Monad](r: => Response): Mapper.Aux[F, HNil, Response] = instance(_.mapOutput(_ => Output.payload(r, r.status))) - implicit def mapperFromKindToEffectOutputFunction[A, B, F[_], G[_]: Effect](f: A => F[Output[B]])( - implicit conv: ToEffect[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[_]: Effect](f: => F[Output[B]])( - implicit conv: ToEffect[F, G]): Mapper.Aux[G, A, B] = instance(_.mapOutputAsync(a => 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(a => conv.apply(f))) - implicit def mapperFromKindToEffectResponsFunction[A, F[_], G[_]: Effect](f: A => F[Response])( - implicit conv: ToEffect[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[_]: Effect](f: => F[Response])( - implicit conv: ToEffect[F, G]): Mapper.Aux[G, A, Response] = + 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[_]: Effect, G[_], A, B, FN, FOB](f: FN)(implicit + 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: ToEffect[G, F] + conv: ToAsync[G, F] ): Mapper.Aux[F, A, B] = instance(_.mapOutputAsync(a => conv.apply(ev(ftp(f)(a))))) - implicit def mapperFromKindResponseHFunction[F[_] : Effect, G[_], A, FN, FR](f: FN)(implicit + implicit def mapperFromKindResponseHFunction[F[_] : Async, G[_], A, FN, FR](f: FN)(implicit ftp: FnToProduct.Aux[FN, A => FR], ev: FR <:< G[Response], - conv: ToEffect[G, F] + 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/ToEffect.scala b/core/src/main/scala/io/finch/internal/ToAsync.scala similarity index 58% rename from core/src/main/scala/io/finch/internal/ToEffect.scala rename to core/src/main/scala/io/finch/internal/ToAsync.scala index 681fdc26a..2f1c68010 100644 --- a/core/src/main/scala/io/finch/internal/ToEffect.scala +++ b/core/src/main/scala/io/finch/internal/ToAsync.scala @@ -1,22 +1,22 @@ package io.finch.internal -import cats.effect.Effect +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 ToEffect[A[_], B[_]] extends ~>[A, B] +trait ToAsync[A[_], B[_]] extends ~>[A, B] -object ToEffect { +object ToAsync { - implicit def idEffect[E[_]: Effect]: ToEffect[E, E] = new ToEffect[E, E] { + implicit def idAsync[E[_]: Async]: ToAsync[E, E] = new ToAsync[E, E] { def apply[A](a: E[A]): E[A] = a } - implicit def twFutureToEffect[E[_]: Effect]: ToEffect[TwitterFuture, E] = new ToEffect[TwitterFuture, E] { + implicit def twFutureToAsync[E[_]: Async]: ToAsync[TwitterFuture, E] = new ToAsync[TwitterFuture, E] { def apply[A](a: TwitterFuture[A]): E[A] = - Effect[E].async { cb => + Async[E].async { cb => a.respond { case Return(r) => cb(Right(r)) case Throw(t) => cb(Left(t)) @@ -24,9 +24,9 @@ object ToEffect { } } - implicit def scFutureToIO[E[_]: Effect]: ToEffect[ScalaFuture, E] = new ToEffect[ScalaFuture, E] { + implicit def scFutureToAsync[E[_]: Async]: ToAsync[ScalaFuture, E] = new ToAsync[ScalaFuture, E] { def apply[A](a: ScalaFuture[A]): E[A] = - Effect[E].async { cb => + Async[E].async { cb => a.onComplete { case Success(s) => cb(Right(s)) case Failure(t) => cb(Left(t)) diff --git a/core/src/main/scala/io/finch/package.scala b/core/src/main/scala/io/finch/package.scala index a2e4a3ee3..a768c80b8 100644 --- a/core/src/main/scala/io/finch/package.scala +++ b/core/src/main/scala/io/finch/package.scala @@ -8,7 +8,7 @@ import cats.effect.IO */ package object finch extends Outputs with ValidationRules { - type ToEffect[F[_], E[_]] = internal.ToEffect[F, E] + type ToAsync[F[_], E[_]] = internal.ToAsync[F, E] object catsEffect extends EndpointModule[IO] diff --git a/core/src/test/scala/io/finch/EndpointSpec.scala b/core/src/test/scala/io/finch/EndpointSpec.scala index 145178fbb..781dac030 100644 --- a/core/src/test/scala/io/finch/EndpointSpec.scala +++ b/core/src/test/scala/io/finch/EndpointSpec.scala @@ -5,6 +5,8 @@ import java.util.concurrent.TimeUnit import cats.data.NonEmptyList import cats.effect.{IO, Resource} +import cats.laws._ +import cats.laws.discipline._ import cats.laws.discipline.AlternativeTests import cats.laws.discipline.SemigroupalTests.Isomorphisms import com.twitter.finagle.http.{Cookie, Method, Request} @@ -39,6 +41,13 @@ class EndpointSpec extends FinchSpec { } } + it should "correctly run mapF" in { + check { e: Endpoint[IO, String] => + val fn: String => Int = _.length + e.transformF(_.map(fn)) <-> e.map(fn) + } + } + it should "support transform" in { check { i: Input => val fn = (fs: IO[Output[String]]) => fs.map(_.map(_ * 2)) diff --git a/core/src/test/scala/io/finch/MethodSpec.scala b/core/src/test/scala/io/finch/MethodSpec.scala index 519f2e740..b3cf214fd 100644 --- a/core/src/test/scala/io/finch/MethodSpec.scala +++ b/core/src/test/scala/io/finch/MethodSpec.scala @@ -122,7 +122,7 @@ class MethodSpec case class Program[A](value: A) - implicit val conv = new ToEffect[Program,IO] { + implicit val conv = new ToAsync[Program,IO] { def apply[A](a: Program[A]): IO[A] = IO(a.value) } diff --git a/core/src/test/scala/io/finch/internal/ToEffectLaws.scala b/core/src/test/scala/io/finch/internal/ToEffectLaws.scala index ca4ce6f7a..20ba8274e 100644 --- a/core/src/test/scala/io/finch/internal/ToEffectLaws.scala +++ b/core/src/test/scala/io/finch/internal/ToEffectLaws.scala @@ -14,7 +14,7 @@ abstract trait ToEffectLaws[F[_], G[_], A] extends Laws with MissingInstances wi def G: Applicative[G] def extract: G[A] => A - def T: ToEffect[F, G] + def T: ToAsync[F, G] 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))) @@ -31,12 +31,12 @@ 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: ToEffect[F, G] + t: ToAsync[F, G] ): ToEffectLaws[F, G, A] = new ToEffectLaws[F, G, A] { val F: Applicative[F] = implicitly[Applicative[F]] val G: Applicative[G] = implicitly[Applicative[G]] - val T: ToEffect[F, G] = t + val T: ToAsync[F, G] = t val extract: G[A] => A = e } diff --git a/examples/src/main/scala/io/finch/iteratee/Main.scala b/examples/src/main/scala/io/finch/iteratee/Main.scala index 0d7adc07a..b7ac3ae46 100644 --- a/examples/src/main/scala/io/finch/iteratee/Main.scala +++ b/examples/src/main/scala/io/finch/iteratee/Main.scala @@ -73,7 +73,7 @@ object Main extends IOApp { def run(args: List[String]): IO[ExitCode] = { val server = Resource.make(serve)(s => - IO.suspend(implicitly[ToEffect[Future, IO]].apply(s.close())) + IO.suspend(implicitly[ToAsync[Future, IO]].apply(s.close())) ) server.use(_ => IO.never).as(ExitCode.Success) diff --git a/fs2/src/main/scala/io/finch/fs2/package.scala b/fs2/src/main/scala/io/finch/fs2/package.scala index f91a85dd6..5cb6aad6f 100644 --- a/fs2/src/main/scala/io/finch/fs2/package.scala +++ b/fs2/src/main/scala/io/finch/fs2/package.scala @@ -11,7 +11,7 @@ package object fs2 extends StreamInstances { implicit def streamLiftReader[F[_]](implicit F: Effect[F], - TE: ToEffect[Future, F] + TE: ToAsync[Future, F] ): LiftReader[Stream, F] = new LiftReader[Stream, F] { final def apply[A](reader: Reader[Buf], process: Buf => A): Stream[F, A] = { @@ -52,7 +52,7 @@ trait StreamInstances { protected abstract class EncodeFs2Stream[F[_], A, CT <: String](implicit F: Effect[F], - TE: ToEffect[Future, F] + TE: ToAsync[Future, F] ) extends EncodeStream[F, Stream, A] with (Either[Throwable, Unit] => IO[Unit]) { type ContentType = CT diff --git a/iteratee/src/main/scala/io/finch/iteratee/package.scala b/iteratee/src/main/scala/io/finch/iteratee/package.scala index 8fc25b140..c675edb4f 100644 --- a/iteratee/src/main/scala/io/finch/iteratee/package.scala +++ b/iteratee/src/main/scala/io/finch/iteratee/package.scala @@ -1,6 +1,6 @@ package io.finch -import cats.effect.{Effect, IO} +import cats.effect.{Async, Effect, IO} import com.twitter.io._ import com.twitter.util.Future import io.finch.internal._ @@ -10,8 +10,8 @@ import java.nio.charset.Charset package object iteratee extends IterateeInstances { implicit def enumeratorLiftReader[F[_]](implicit - F: Effect[F], - TE: ToEffect[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] = { @@ -30,19 +30,19 @@ package object iteratee extends IterateeInstances { implicit def encodeJsonEnumerator[F[_]: Effect, A](implicit A: Encode.Json[A], - TE: ToEffect[Future, F] + 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: ToEffect[Future, F] + 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: ToEffect[Future, F] + 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) @@ -54,7 +54,7 @@ trait IterateeInstances { protected final class EncodeNewLineDelimitedEnumerator[F[_]: Effect, A, CT <: String](implicit A: Encode.Aux[A, CT], - TE: ToEffect[Future, F] + TE: ToAsync[Future, F] ) extends EncodeEnumerator[F, A, CT] { protected def encodeChunk(chunk: A, cs: Charset): Buf = A(chunk, cs).concat(newLine(cs)) @@ -62,7 +62,7 @@ trait IterateeInstances { protected abstract class EncodeEnumerator[F[_], A, CT <: String](implicit F: Effect[F], - TE: ToEffect[Future, F] + TE: ToAsync[Future, F] ) extends EncodeStream[F, Enumerator, A] with (Either[Throwable, Unit] => IO[Unit]) { type ContentType = CT