Skip to content

Commit

Permalink
Example on using Endpoint.Compiled (#1083)
Browse files Browse the repository at this point in the history
* Example on using Endpoint.Compiled

* Update examples according to comments
  • Loading branch information
sergeykolbasov authored Feb 25, 2019
1 parent 3d231cf commit 14e2cd6
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 5 deletions.
10 changes: 5 additions & 5 deletions core/src/main/scala/io/finch/Compile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ object Compile {

implicit def hnilTS[F[_]](implicit F: Applicative[F]): Compile[F, HNil, HNil] = new Compile[F, HNil, HNil] {
def apply(es: HNil, opts: Options, ctx: Context): Endpoint.Compiled[F] =
new Endpoint.Compiled[F]((req: Request) => {
Endpoint.Compiled[F] { req: Request =>
val rep = Response()

if (ctx.wouldAllow.nonEmpty && opts.enableMethodNotAllowed) {
Expand All @@ -91,7 +91,7 @@ object Compile {
}

F.pure(Trace.empty -> Right(conformHttp(rep, req.version, opts)))
})
}
}

type IsNegotiable[C] = OrElse[C <:< Coproduct, DummyImplicit]
Expand All @@ -108,15 +108,15 @@ object Compile {
val negotiateContent = isNegotiable.fold(_ => true, _ => false)
val underlying = es.head.handle(handler)

new Endpoint.Compiled[F]((req: Request) => {
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)))
.map(r => conformHttp(r, req.version, opts)))
.attempt
.map(e => trc -> e)

Expand All @@ -126,7 +126,7 @@ object Compile {
case _ =>
tsT(es.tail, opts, ctx)(req)
}
})
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/io/finch/Endpoint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,13 @@ object Endpoint {
*/
type Compiled[F[_]] = Kleisli[F, Request, (Trace, Either[Throwable, Response])]

object Compiled {

def apply[F[_]](run: Request => F[(Trace, Either[Throwable, Response])]): Endpoint.Compiled[F] =
new Endpoint.Compiled[F](run)

}

final implicit 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
Expand Down
65 changes: 65 additions & 0 deletions examples/src/main/scala/io/finch/middleware/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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}
import io.finch._

/**
* Small Finch hello world application serving endpoint protected by serious authentication
* where each request & response are also logged and measured.
*
* This is achieved using Kleisli-based middleware together with [[Bootstrap.compile]]
*
* Use the following curl commands to test it:
*
* {{{
* curl -v -H "Authorization: secret" http://localhost:8081/hello
* curl -V -H "Authorization: wrong" http://localhost:8081/hello
* }}}
*
* !Disclaimer: most likely you would need to use proper libraries for logging, auth, and metrics
* instead but their use will be quite similar.
*/
object Main extends App with Endpoint.Module[IO] {

val helloWorld: Endpoint[IO, String] = get("hello") {
Ok("Hello world")
}

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)))
}
}

val logging: Endpoint.Compiled[IO] => Endpoint.Compiled[IO] = compiled => {
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 => {
val now = IO(Time.now)
Endpoint.Compiled[IO] { req =>
for {
start <- now
traceAndResponse <- compiled(req)
(trace, response) = traceAndResponse
stop <- now
_ <- IO(print(s"Response time: ${stop.diff(start)}. Trace: $trace\n"))
} yield {
(trace, response)
}
}
}

val filters = Function.chain(Seq(stats, logging, auth))
val compiled = filters(Bootstrap.serve[Text.Plain](helloWorld).compile)

Await.ready(Http.server.serve(":8081", Endpoint.toService(compiled)))

}

0 comments on commit 14e2cd6

Please sign in to comment.