Last active
July 23, 2019 13:10
-
-
Save jamesanto/971704b57e9a595d87525f16a83009fd to your computer and use it in GitHub Desktop.
Generic Services & Filters based on Finagle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait FlatMap[F[_]] { | |
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] | |
} | |
object OptionFlatMap extends FlatMap[Option]{ | |
override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa.flatMap(f) | |
} | |
//example for option | |
object option { | |
implicit val optionFlatMap: FlatMap[Option] = OptionFlatMap | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Contains an abstraction for Services(Transformers) & Filters. | |
* Inspired by finagle, but generalized for common usage | |
* Read about it here : https://twitter.github.io/finagle/guide/ServicesAndFilters.html | |
* And check the test cases for example | |
*/ | |
package object service { | |
type Service[-Req, Rep, F[_]] = Req => F[Rep] | |
type Filter[ReqIn, RepOut, ReqOut, RepIn, F[_]] = (ReqIn, ReqOut => F[RepIn]) => F[RepOut] | |
type SimpleFilter[Req, Rep, F[_]] = Filter[Req, Rep, Req, Rep, F] | |
type ServiceO[-Req, Rep] = Service[Req, Rep, Option] | |
type ServiceF[-Req, Rep] = Service[Req, Rep, Future] | |
type ServiceT[-Req, Rep] = Service[Req, Rep, Try] | |
type FilterO[ReqIn, RepOut, ReqOut, RepIn] = Filter[ReqIn, RepOut, ReqOut, RepIn, Option] | |
type FilterF[ReqIn, RepOut, ReqOut, RepIn] = Filter[ReqIn, RepOut, ReqOut, RepIn, Future] | |
type FilterT[ReqIn, RepOut, ReqOut, RepIn] = Filter[ReqIn, RepOut, ReqOut, RepIn, Try] | |
type SimpleFilterO[Req, Rep] = SimpleFilter[Req, Rep, Option] | |
type SimpleFilterF[Req, Rep] = SimpleFilter[Req, Rep, Future] | |
type SimpleFilterT[Req, Rep] = SimpleFilter[Req, Rep, Try] | |
implicit class RichFilter[ReqIn, RepOut, ReqOut, RepIn, F[_]](val filter: (ReqIn, ReqOut => F[RepIn]) => F[RepOut]) { | |
def and(service: ReqOut => F[RepIn]): ReqIn => F[RepOut] = { reqIn => | |
filter(reqIn, service) | |
} | |
def ->>(service: ReqOut => F[RepIn]): ReqIn => F[RepOut] = and(service) | |
def and[Req2, Rep2](next: (ReqOut, Req2 => F[Rep2]) => F[RepIn]): (ReqIn, Req2 => F[Rep2]) => F[RepOut] = AndThen(service => and(next.and(service))) | |
def ->>[Req2, Rep2](next: (ReqOut, Req2 => F[Rep2]) => F[RepIn]): (ReqIn, Req2 => F[Rep2]) => F[RepOut] = and(next) | |
} | |
private case class AndThen[ReqIn, RepOut, ReqOut, RepIn, F[_]]( | |
build: (ReqOut => F[RepIn]) => ReqIn => F[RepOut] | |
) | |
extends ((ReqIn, ReqOut => F[RepIn]) => F[RepOut]) { | |
def apply(request: ReqIn, service: ReqOut => F[RepIn]): F[RepOut] = | |
build(service)(request) | |
} | |
implicit class RichService[-Req, Rep, F[_] : FlatMap](val service: Req => F[Rep]) { | |
def and[Rep2](next: Rep => F[Rep2]): Req => F[Rep2] = { req => | |
implicitly[FlatMap[F]].flatMap(service(req))(next) | |
} | |
def map[Req2](f: Req2 => Req): Req2 => F[Rep] = { req => | |
service(f(req)) | |
} | |
def ->>[Rep2](next: Rep => F[Rep2]): Req => F[Rep2] = and(next) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalatest.{FreeSpec, Matchers} | |
class ServicesAndFiltersBasicTest extends FreeSpec with Matchers { | |
def upperCase(s: String): Option[String] = Option(s).map(_.toUpperCase) | |
def doubleIt(s: String): Option[String] = Option(s).map(_ * 2) | |
def min6Letters(request: String, service: String => Option[String]): Option[String] = { | |
if (request.length > 5) { | |
service(request) | |
} else None | |
} | |
def containsHello(request: String, service: String => Option[String]): Option[String] = { | |
if (request.contains("hello")) { | |
service(request) | |
} else None | |
} | |
"should work properly" in { | |
val all = containsHello _ ->> | |
min6Letters _ ->> | |
min6Letters _ ->> | |
upperCase _ ->> | |
upperCase ->> | |
doubleIt | |
all("hello world") shouldBe Some("HELLO WORLDHELLO WORLD") | |
all("hello") shouldBe None | |
} | |
"map functionality should work properly" in { | |
val service = doubleIt _ | |
val doubleInt = service.map[Int](_.toString) | |
doubleInt(123) shouldBe Some("123123") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See this for ZIO based version : https://gist.github.com/jamesanto/58b44fd630b502e95c8cccaadc27854d