-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sample http4s REST client/server with client macro derivation #552
Changes from 1 commit
ed9cf04
93f8ace
b03637c
2a1cc42
a74604e
6424dab
8caee87
65d705d
98df299
a216c63
051519d
8eb36b4
c172539
20aae64
15abe94
5d5b726
683147c
54990d8
fe34e47
1132fc2
97d1c73
6db7ab3
2a9cd3d
06f2b83
3888be7
965fdd4
d6ce882
3133ec5
34c0504
795eb12
c922e0f
9b0a8f3
fb91483
854b0d6
f6eab68
ad90c2d
f1f60ff
94dba66
bf6e853
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,6 @@ package higherkindness.mu.http | |
import cats.ApplicativeError | ||
import cats.effect._ | ||
import cats.implicits._ | ||
import fs2.interop.reactivestreams._ | ||
import fs2.{RaiseThrowable, Stream} | ||
import io.grpc.Status.Code._ | ||
import org.typelevel.jawn.ParseException | ||
|
@@ -29,24 +28,22 @@ import io.circe.jawn.CirceSupportParser.facade | |
import io.circe.syntax._ | ||
import io.grpc.{Status => _, _} | ||
import jawnfs2._ | ||
import monix.execution._ | ||
import monix.reactive.Observable | ||
import org.http4s._ | ||
import org.http4s.dsl.Http4sDsl | ||
import scala.concurrent.ExecutionContext | ||
import org.http4s.Status.Ok | ||
import scala.util.control.NoStackTrace | ||
|
||
object Utils { | ||
object implicits { | ||
|
||
implicit class MessageOps[F[_]](val message: Message[F]) extends AnyVal { | ||
implicit class MessageOps[F[_]](private val message: Message[F]) extends AnyVal { | ||
|
||
def jsonBodyAsStream[A]( | ||
implicit decoder: Decoder[A], | ||
F: ApplicativeError[F, Throwable]): Stream[F, A] = | ||
message.body.chunks.parseJsonStream.map(_.as[A]).rethrow | ||
} | ||
|
||
implicit class RequestOps[F[_]](val request: Request[F]) { | ||
implicit class RequestOps[F[_]](private val request: Request[F]) { | ||
|
||
def asStream[A](implicit decoder: Decoder[A], F: ApplicativeError[F, Throwable]): Stream[F, A] = | ||
request | ||
|
@@ -57,7 +54,7 @@ object Utils { | |
} | ||
} | ||
|
||
implicit class ResponseOps[F[_]](val response: Response[F]) { | ||
implicit class ResponseOps[F[_]](private val response: Response[F]) { | ||
|
||
implicit private val throwableDecoder: Decoder[Throwable] = | ||
Decoder.decodeTuple2[String, String].map { | ||
|
@@ -73,29 +70,21 @@ object Utils { | |
implicit decoder: Decoder[A], | ||
F: ApplicativeError[F, Throwable], | ||
R: RaiseThrowable[F]): Stream[F, A] = | ||
if (response.status.code != 200) Stream.raiseError(ResponseError(response.status)) | ||
if (response.status.code != Ok.code) Stream.raiseError(ResponseError(response.status)) | ||
else response.jsonBodyAsStream[Either[Throwable, A]].rethrow | ||
} | ||
|
||
implicit class Fs2StreamOps[F[_], A](stream: Stream[F, A]) { | ||
implicit class Fs2StreamOps[F[_], A](private val stream: Stream[F, A]) { | ||
|
||
implicit private val throwableEncoder: Encoder[Throwable] = new Encoder[Throwable] { | ||
implicit val throwableEncoder: Encoder[Throwable] = new Encoder[Throwable] { | ||
def apply(ex: Throwable): Json = (ex.getClass.getName, ex.getMessage).asJson | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the above: implicit val unexpectedErrorEncoder: Encoder[UnexpectedError] = deriveEncoder |
||
|
||
def asJsonEither(implicit encoder: Encoder[A]): Stream[F, Json] = stream.attempt.map(_.asJson) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. def asJsonEither(implicit encoder: Encoder[A]): Stream[F, Json] =
stream.attempt.bimap(e => UnexpectedError(e.getClass.getName, Option(e.getMessage)), _.asJson) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry @fedefernandez, I don't really understand how you expect to instantiate the Maybe you're proposing case class UnexpectedError(status: String, msg: Option[String] = None) instead of case class UnexpectedError(status: Status, msg: Option[String] = None) |
||
|
||
def toObservable(implicit F: ConcurrentEffect[F], ec: ExecutionContext): Observable[A] = | ||
Observable.fromReactivePublisher(stream.toUnicastPublisher) | ||
} | ||
|
||
implicit class MonixStreamOps[A](val stream: Observable[A]) extends AnyVal { | ||
|
||
def toFs2Stream[F[_]](implicit F: ConcurrentEffect[F], sc: Scheduler): Stream[F, A] = | ||
stream.toReactivePublisher.toStream[F]() | ||
} | ||
|
||
implicit class FResponseOps[F[_]: Sync](response: F[Response[F]]) extends Http4sDsl[F] { | ||
implicit class FResponseOps[F[_]: Sync](private val response: F[Response[F]]) | ||
extends Http4sDsl[F] { | ||
|
||
def adaptErrors: F[Response[F]] = response.handleErrorWith { | ||
case se: StatusException => errorFromStatus(se.getStatus, se.getMessage) | ||
|
@@ -106,7 +95,7 @@ object Utils { | |
private def errorFromStatus(status: io.grpc.Status, message: String): F[Response[F]] = | ||
status.getCode match { | ||
case INVALID_ARGUMENT => BadRequest(message) | ||
case UNAUTHENTICATED => BadRequest(message) | ||
case UNAUTHENTICATED => Forbidden(message) | ||
case PERMISSION_DENIED => Forbidden(message) | ||
case NOT_FOUND => NotFound(message) | ||
case UNAVAILABLE => ServiceUnavailable(message) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, help me to understand this. This is only used for streams and observable, right? The server generates an Either that is parsed in the client. Am I right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
implicits.scala
was enterally created by @L-Lavigne, so I'll let him answer all your questions.