Skip to content

Instantly share code, notes, and snippets.

@jamesanto
Last active July 23, 2019 13:10
Show Gist options
  • Save jamesanto/971704b57e9a595d87525f16a83009fd to your computer and use it in GitHub Desktop.
Save jamesanto/971704b57e9a595d87525f16a83009fd to your computer and use it in GitHub Desktop.
Generic Services & Filters based on Finagle
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
}
/**
* 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)
}
}
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")
}
}
@jamesanto
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment