From 4fe86d1a03215c918a400e2030e8891429157396 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 6 Jan 2022 16:48:05 +0530 Subject: [PATCH 1/2] refactor: remove type-params from Response --- .../example/PlainTextBenchmarkServer.scala | 4 +- .../scala/example/WebSocketAdvanced.scala | 10 +- .../main/scala/example/WebSocketEcho.scala | 8 +- .../scala/zhttp/endpoint/CanConstruct.scala | 14 +-- .../main/scala/zhttp/http/CanBeSilenced.scala | 4 +- zio-http/src/main/scala/zhttp/http/Http.scala | 8 +- .../src/main/scala/zhttp/http/HttpError.scala | 2 +- .../main/scala/zhttp/http/Middleware.scala | 2 +- .../src/main/scala/zhttp/http/Patch.scala | 6 +- .../src/main/scala/zhttp/http/Response.scala | 97 +++++++++++-------- .../src/main/scala/zhttp/http/Status.scala | 4 +- .../src/main/scala/zhttp/http/package.scala | 8 +- .../main/scala/zhttp/service/Handler.scala | 6 +- .../service/server/WebSocketUpgrade.scala | 4 +- .../main/scala/zhttp/socket/Conversion.scala | 9 -- .../main/scala/zhttp/socket/IsWebSocket.scala | 6 +- .../src/main/scala/zhttp/socket/Socket.scala | 5 + .../main/scala/zhttp/socket/SocketApp.scala | 14 +-- .../src/main/scala/zhttp/socket/package.scala | 2 +- .../test/scala/zhttp/internal/HttpGen.scala | 2 +- .../zhttp/middleware/MiddlewareSpec.scala | 2 +- .../test/scala/zhttp/service/ServerSpec.scala | 2 +- .../zhttp/service/WebSocketServerSpec.scala | 7 +- 23 files changed, 117 insertions(+), 109 deletions(-) delete mode 100644 zio-http/src/main/scala/zhttp/socket/Conversion.scala diff --git a/example/src/main/scala/example/PlainTextBenchmarkServer.scala b/example/src/main/scala/example/PlainTextBenchmarkServer.scala index 5563f287b1..33af49d087 100644 --- a/example/src/main/scala/example/PlainTextBenchmarkServer.scala +++ b/example/src/main/scala/example/PlainTextBenchmarkServer.scala @@ -28,9 +28,9 @@ object Main extends App { .exitCode } - private def app(response: Response[Any, Nothing]) = Http.response(response) + private def app(response: Response) = Http.response(response) - private def server(response: Response[Any, Nothing]) = + private def server(response: Response) = Server.app(app(response)) ++ Server.port(8080) ++ Server.error(_ => UIO.unit) ++ diff --git a/example/src/main/scala/example/WebSocketAdvanced.scala b/example/src/main/scala/example/WebSocketAdvanced.scala index 00c2e1a69f..48e34955d4 100644 --- a/example/src/main/scala/example/WebSocketAdvanced.scala +++ b/example/src/main/scala/example/WebSocketAdvanced.scala @@ -2,10 +2,10 @@ package example import zhttp.http._ import zhttp.service.Server -import zhttp.socket.{Socket, SocketApp, SocketDecoder, SocketProtocol, WebSocketFrame} +import zhttp.socket._ +import zio._ import zio.duration._ import zio.stream.ZStream -import zio.{App, ExitCode, Schedule, URIO, console} object WebSocketAdvanced extends App { // Message Handlers @@ -48,9 +48,9 @@ object WebSocketAdvanced extends App { } private val app = - Http.collect[Request] { - case Method.GET -> !! / "greet" / name => Response.text(s"Greetings ${name}!") - case Method.GET -> !! / "subscriptions" => Response.socket(socketApp) + Http.collectZIO[Request] { + case Method.GET -> !! / "greet" / name => Response.text(s"Greetings ${name}!").wrapZIO + case Method.GET -> !! / "subscriptions" => socketApp.toResponse } override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = diff --git a/example/src/main/scala/example/WebSocketEcho.scala b/example/src/main/scala/example/WebSocketEcho.scala index 27731b1b03..403b6516e7 100644 --- a/example/src/main/scala/example/WebSocketEcho.scala +++ b/example/src/main/scala/example/WebSocketEcho.scala @@ -1,6 +1,6 @@ package example -import zhttp.http.{Method, Response, _} +import zhttp.http._ import zhttp.service.Server import zhttp.socket.{Socket, WebSocketFrame} import zio.duration._ @@ -18,9 +18,9 @@ object WebSocketEcho extends App { } private val app = - Http.collect[Request] { - case Method.GET -> !! / "greet" / name => Response.text(s"Greetings {$name}!") - case Method.GET -> !! / "subscriptions" => Response.socket(socket) + Http.collectZIO[Request] { + case Method.GET -> !! / "greet" / name => Response.text(s"Greetings {$name}!").wrapZIO + case Method.GET -> !! / "subscriptions" => socket.toResponse } override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = diff --git a/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala b/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala index c640d64a17..81d148ff29 100644 --- a/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala +++ b/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala @@ -18,11 +18,11 @@ object CanConstruct { type EOut = E } - implicit def response[R, E, A]: Aux[R, E, A, Response[R, E]] = new CanConstruct[A, Response[R, E]] { - override type ROut = R - override type EOut = E + implicit def response[A]: Aux[Any, Nothing, A, Response] = new CanConstruct[A, Response] { + override type ROut = Any + override type EOut = Nothing - override def make(route: Endpoint[A], f: Request.ParameterizedRequest[A] => Response[R, E]): HttpApp[R, E] = + override def make(route: Endpoint[A], f: Request.ParameterizedRequest[A] => Response): HttpApp[Any, Nothing] = Http .collectHttp[Request] { case req => route.extract(req) match { @@ -32,14 +32,14 @@ object CanConstruct { } } - implicit def responseZIO[R, E, A]: Aux[R, E, A, ZIO[R, E, Response[R, E]]] = - new CanConstruct[A, ZIO[R, E, Response[R, E]]] { + implicit def responseZIO[R, E, A]: Aux[R, E, A, ZIO[R, E, Response]] = + new CanConstruct[A, ZIO[R, E, Response]] { override type ROut = R override type EOut = E override def make( route: Endpoint[A], - f: Request.ParameterizedRequest[A] => ZIO[R, E, Response[R, E]], + f: Request.ParameterizedRequest[A] => ZIO[R, E, Response], ): HttpApp[R, E] = { Http .collectHttp[Request] { case req => diff --git a/zio-http/src/main/scala/zhttp/http/CanBeSilenced.scala b/zio-http/src/main/scala/zhttp/http/CanBeSilenced.scala index a7aa435280..767f706ae1 100644 --- a/zio-http/src/main/scala/zhttp/http/CanBeSilenced.scala +++ b/zio-http/src/main/scala/zhttp/http/CanBeSilenced.scala @@ -5,8 +5,8 @@ trait CanBeSilenced[-E, +A] { } object CanBeSilenced { - implicit object SilenceHttpError extends CanBeSilenced[Throwable, UResponse] { - override def silent(e: Throwable): UResponse = e match { + implicit object SilenceHttpError extends CanBeSilenced[Throwable, Response] { + override def silent(e: Throwable): Response = e match { case m: HttpError => m.toResponse case m => Response.fromHttpError(HttpError.InternalServerError("Internal Server Error", Option(m))) } diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index c9d8b35ba7..1b5b7ededc 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -382,7 +382,7 @@ object Http { * Converts a failing Http app into a non-failing one by handling the failure and converting it to a result if * possible. */ - def silent[R1 <: R, E1 >: E](implicit s: CanBeSilenced[E1, Response[R1, E1]]): HttpApp[R1, E1] = + def silent[R1 <: R, E1 >: E](implicit s: CanBeSilenced[E1, Response]): HttpApp[R1, E1] = http.catchAll(e => Http.succeed(s.silent(e))) /** @@ -463,7 +463,7 @@ object Http { /** * Creates an Http app which always responds the provided data and a 200 status code */ - def fromData(data: HttpData) = response(Response(data = data)) + def fromData(data: HttpData): HttpApp[Any, Nothing] = response(Response(data = data)) /** * Converts a ZIO to an Http type @@ -532,12 +532,12 @@ object Http { /** * Creates an Http app which always responds with the same value. */ - def response[R, E](response: Response[R, E]): HttpApp[R, E] = Http.succeed(response) + def response[R, E](response: Response): HttpApp[R, E] = Http.succeed(response) /** * Converts a ZIO to an Http app type */ - def responseZIO[R, E](res: ZIO[R, E, Response[R, E]]): HttpApp[R, E] = Http.fromEffect(res) + def responseZIO[R, E](res: ZIO[R, E, Response]): HttpApp[R, E] = Http.fromEffect(res) /** * Creates an Http that delegates to other Https. diff --git a/zio-http/src/main/scala/zhttp/http/HttpError.scala b/zio-http/src/main/scala/zhttp/http/HttpError.scala index e24022fe6f..71999c77c4 100644 --- a/zio-http/src/main/scala/zhttp/http/HttpError.scala +++ b/zio-http/src/main/scala/zhttp/http/HttpError.scala @@ -1,7 +1,7 @@ package zhttp.http sealed abstract class HttpError(val status: Status, val message: String) extends Throwable(message) { self => - def toResponse: UResponse = Response.fromHttpError(self) + def toResponse: Response = Response.fromHttpError(self) } object HttpError { diff --git a/zio-http/src/main/scala/zhttp/http/Middleware.scala b/zio-http/src/main/scala/zhttp/http/Middleware.scala index 9f12d28bbc..c1e55d02bf 100644 --- a/zio-http/src/main/scala/zhttp/http/Middleware.scala +++ b/zio-http/src/main/scala/zhttp/http/Middleware.scala @@ -407,7 +407,7 @@ object Middleware { case OrElse(self, other) => Http.fromOptionFunction { req => (self.execute(app, flag)(req) orElse other.execute(app, flag)(req)) - .asInstanceOf[ZIO[R, Option[E], Response[R, E]]] + .asInstanceOf[ZIO[R, Option[E], Response]] } } diff --git a/zio-http/src/main/scala/zhttp/http/Patch.scala b/zio-http/src/main/scala/zhttp/http/Patch.scala index d183111fbf..7ad0b0ec00 100644 --- a/zio-http/src/main/scala/zhttp/http/Patch.scala +++ b/zio-http/src/main/scala/zhttp/http/Patch.scala @@ -6,11 +6,11 @@ import scala.annotation.tailrec * Models the set of operations that one would want to apply on a Response. */ sealed trait Patch { self => - def ++(that: Patch): Patch = Patch.Combine(self, that) - def apply[R, E](res: Response[R, E]): Response[R, E] = { + def ++(that: Patch): Patch = Patch.Combine(self, that) + def apply[R, E](res: Response): Response = { @tailrec - def loop[R1, E1](res: Response[R1, E1], patch: Patch): Response[R1, E1] = + def loop[R1, E1](res: Response, patch: Patch): Response = patch match { case Patch.Empty => res case Patch.AddHeaders(headers) => res.addHeaders(headers) diff --git a/zio-http/src/main/scala/zhttp/http/Response.scala b/zio-http/src/main/scala/zhttp/http/Response.scala index 0542d0edcb..e7a9f4dc38 100644 --- a/zio-http/src/main/scala/zhttp/http/Response.scala +++ b/zio-http/src/main/scala/zhttp/http/Response.scala @@ -7,23 +7,23 @@ import zhttp.core.Util import zhttp.html.Html import zhttp.http.HttpError.HTTPErrorWithCause import zhttp.http.headers.HeaderExtension -import zhttp.socket.{Socket, SocketApp, WebSocketFrame} -import zio.{Chunk, UIO} +import zhttp.socket.{IsWebSocket, Socket, SocketApp} +import zio.{Chunk, UIO, ZIO} import java.nio.charset.Charset import java.nio.file.Files -final case class Response[-R, +E] private ( +final case class Response private ( status: Status, headers: Headers, data: HttpData, - private[zhttp] val attribute: Response.Attribute[R, E], -) extends HeaderExtension[Response[R, E]] { self => + private[zhttp] val attribute: Response.Attribute, +) extends HeaderExtension[Response] { self => /** * Adds cookies in the response headers. */ - def addCookie(cookie: Cookie): Response[R, E] = + def addCookie(cookie: Cookie): Response = self.copy(headers = self.getHeaders ++ Headers(HttpHeaderNames.SET_COOKIE.toString, cookie.encode)) /** @@ -33,7 +33,7 @@ final case class Response[-R, +E] private ( * modified the server will detect the changes and encode the response again, however it will turn out to be counter * productive. */ - def freeze: UIO[Response[R, E]] = + def freeze: UIO[Response] = UIO(self.copy(attribute = self.attribute.withEncodedResponse(unsafeEncode(), self))) override def getHeaders: Headers = headers @@ -41,25 +41,30 @@ final case class Response[-R, +E] private ( /** * Sets the response attributes */ - def setAttribute[R1 <: R, E1 >: E](attribute: Response.Attribute[R1, E1]): Response[R1, E1] = + def setAttribute(attribute: Response.Attribute): Response = self.copy(attribute = attribute) /** * Sets the status of the response */ - def setStatus(status: Status): Response[R, E] = + def setStatus(status: Status): Response = self.copy(status = status) /** * Updates the headers using the provided function */ - override def updateHeaders(update: Headers => Headers): Response[R, E] = + override def updateHeaders(update: Headers => Headers): Response = self.copy(headers = update(self.getHeaders)) /** * A more efficient way to append server-time to the response headers. */ - def withServerTime: Response[R, E] = self.copy(attribute = self.attribute.withServerTime) + def withServerTime: Response = self.copy(attribute = self.attribute.withServerTime) + + /** + * Wraps the current response into a ZIO + */ + def wrapZIO: UIO[Response] = UIO(self) /** * Encodes the Response into a Netty HttpResponse. Sets default headers such as `content-length`. For performance @@ -105,15 +110,14 @@ final case class Response[-R, +E] private ( } object Response { - def apply[R, E]( status: Status = Status.OK, headers: Headers = Headers.empty, data: HttpData = HttpData.Empty, - ): Response[R, E] = + ): Response = Response(status, headers, data, Attribute.empty) - def fromHttpError(error: HttpError): UResponse = { + def fromHttpError(error: HttpError): Response = { error match { case cause: HTTPErrorWithCause => Response( @@ -129,10 +133,33 @@ object Response { } } + /** + * Creates a new response for the provided socket + */ + def fromSocket[R, E, A, B](socket: Socket[R, E, A, B])(implicit + ev: IsWebSocket[R, E, A, B], + ): ZIO[R, Nothing, Response] = + fromSocketApp(socket.toSocketApp) + + /** + * Creates a new response for the provided socket app + */ + def fromSocketApp[R](app: SocketApp[R]): ZIO[R, Nothing, Response] = { + ZIO.environment[R].map { env => + Response( + Status.SWITCHING_PROTOCOLS, + Headers.empty, + HttpData.empty, + Attribute(socketApp = Option(app.provide(env))), + ) + } + + } + /** * Creates a response with content-type set to text/html */ - def html(data: Html): UResponse = + def html(data: Html): Response = Response( data = HttpData.fromString("" + data.encode), headers = Headers(HeaderNames.contentType, HeaderValues.textHtml), @@ -143,12 +170,12 @@ object Response { status: Status = Status.OK, headers: Headers = Headers.empty, data: HttpData = HttpData.empty, - ): Response[R, E] = Response(status, headers, data) + ): Response = Response(status, headers, data) /** * Creates a response with content-type set to application/json */ - def json(data: String): UResponse = + def json(data: String): Response = Response( data = HttpData.fromChunk(Chunk.fromArray(data.getBytes(HTTP_CHARSET))), headers = Headers(HeaderNames.contentType, HeaderValues.applicationJson), @@ -157,37 +184,25 @@ object Response { /** * Creates an empty response with status 200 */ - def ok: UResponse = Response(Status.OK) + def ok: Response = Response(Status.OK) /** * Creates an empty response with status 301 or 302 depending on if it's permanent or not. */ - def redirect(location: String, isPermanent: Boolean = false): Response[Any, Nothing] = { + def redirect(location: String, isPermanent: Boolean = false): Response = { val status = if (isPermanent) Status.PERMANENT_REDIRECT else Status.TEMPORARY_REDIRECT Response(status, Headers.location(location)) } - /** - * Creates a socket response using an app - */ - def socket[R, E](app: SocketApp[R]): Response[R, E] = - Response(Status.SWITCHING_PROTOCOLS, Headers.empty, HttpData.empty, Attribute(socketApp = Option(app))) - - /** - * Creates a new WebSocket Response - */ - def socket[R](ss: Socket[R, Throwable, WebSocketFrame, WebSocketFrame]): Response[R, Nothing] = - SocketApp(ss).asResponse - /** * Creates an empty response with the provided Status */ - def status(status: Status): UResponse = Response(status) + def status(status: Status): Response = Response(status) /** * Creates a response with content-type set to text/plain */ - def text(text: String, charset: Charset = HTTP_CHARSET): UResponse = + def text(text: String, charset: Charset = HTTP_CHARSET): Response = Response( data = HttpData.fromString(text, charset), headers = Headers(HeaderNames.contentType, HeaderValues.textPlain), @@ -197,20 +212,20 @@ object Response { * Attribute holds meta data for the backend */ - private[zhttp] final case class Attribute[-R, +E]( - socketApp: Option[SocketApp[R]] = None, + private[zhttp] final case class Attribute( + socketApp: Option[SocketApp[Any]] = None, memoize: Boolean = false, serverTime: Boolean = false, - encoded: Option[(Response[R, E], HttpResponse)] = None, + encoded: Option[(Response, HttpResponse)] = None, ) { self => - def withEncodedResponse[R1 <: R, E1 >: E](jResponse: HttpResponse, response: Response[R1, E1]): Attribute[R1, E1] = + def withEncodedResponse(jResponse: HttpResponse, response: Response): Attribute = self.copy(encoded = Some(response -> jResponse)) - def withMemoization: Attribute[R, E] = self.copy(memoize = true) + def withMemoization: Attribute = self.copy(memoize = true) - def withServerTime: Attribute[R, E] = self.copy(serverTime = true) + def withServerTime: Attribute = self.copy(serverTime = true) - def withSocketApp[R1 <: R](app: SocketApp[R1]): Attribute[R1, E] = self.copy(socketApp = Option(app)) + def withSocketApp(app: SocketApp[Any]): Attribute = self.copy(socketApp = Option(app)) } object Attribute { @@ -218,6 +233,6 @@ object Response { /** * Helper to create an empty HttpData */ - def empty: Attribute[Any, Nothing] = Attribute() + def empty: Attribute = Attribute() } } diff --git a/zio-http/src/main/scala/zhttp/http/Status.scala b/zio-http/src/main/scala/zhttp/http/Status.scala index 901dc1b6c3..51a0c4155f 100644 --- a/zio-http/src/main/scala/zhttp/http/Status.scala +++ b/zio-http/src/main/scala/zhttp/http/Status.scala @@ -10,9 +10,9 @@ sealed trait Status extends Product with Serializable { self => def toApp: UHttpApp = Http.status(self) /** - * Returns a Response[Any, Nothing] with empty data and no headers. + * Returns a Response with empty data and no headers. */ - def toResponse: UResponse = Response(self) + def toResponse: Response = Response(self) /** * Returns self as io.netty.handler.codec.http.HttpResponseStatus. diff --git a/zio-http/src/main/scala/zhttp/http/package.scala b/zio-http/src/main/scala/zhttp/http/package.scala index 510cf1c622..7c6adc1c2d 100644 --- a/zio-http/src/main/scala/zhttp/http/package.scala +++ b/zio-http/src/main/scala/zhttp/http/package.scala @@ -6,14 +6,12 @@ import zio.ZIO import java.nio.charset.Charset package object http extends PathModule with RequestSyntax with RouteDecoderModule { - type HttpApp[-R, +E] = Http[R, E, Request, Response[R, E]] + type HttpApp[-R, +E] = Http[R, E, Request, Response] type UHttpApp = HttpApp[Any, Nothing] type RHttpApp[-R] = HttpApp[R, Throwable] type UHttp[-A, +B] = Http[Any, Nothing, A, B] - type SilentResponse[-E] = CanBeSilenced[E, UResponse] - type UResponse = Response[Any, Nothing] - type UHttpResponse = Response[Any, Nothing] - type ResponseZIO[-R, +E] = ZIO[R, E, Response[R, E]] + type SilentResponse[-E] = CanBeSilenced[E, Response] + type ResponseZIO[-R, +E] = ZIO[R, E, Response] type Header = (CharSequence, CharSequence) /** diff --git a/zio-http/src/main/scala/zhttp/service/Handler.scala b/zio-http/src/main/scala/zhttp/service/Handler.scala index 9cd49a2bf4..f7acbe0189 100644 --- a/zio-http/src/main/scala/zhttp/service/Handler.scala +++ b/zio-http/src/main/scala/zhttp/service/Handler.scala @@ -58,7 +58,7 @@ private[zhttp] final case class Handler[R]( * Checks if an encoded version of the response exists, uses it if it does. Otherwise, it will return a fresh * response. It will also set the server time if requested by the client. */ - private def encodeResponse(res: Response[_, _]): HttpResponse = { + private def encodeResponse(res: Response): HttpResponse = { val jResponse = res.attribute.encoded match { @@ -111,7 +111,7 @@ private[zhttp] final case class Handler[R]( */ private def unsafeRun[A]( jReq: FullHttpRequest, - http: Http[R, Throwable, A, Response[R, Throwable]], + http: Http[R, Throwable, A, Response], a: A, )(implicit ctx: Ctx): Unit = { http.execute(a) match { @@ -185,7 +185,7 @@ private[zhttp] final case class Handler[R]( /** * Writes any response to the Channel */ - private def unsafeWriteAndFlushAnyResponse[A](res: Response[R, Throwable])(implicit ctx: Ctx): Unit = { + private def unsafeWriteAndFlushAnyResponse[A](res: Response)(implicit ctx: Ctx): Unit = { ctx.writeAndFlush(encodeResponse(res)): Unit } diff --git a/zio-http/src/main/scala/zhttp/service/server/WebSocketUpgrade.scala b/zio-http/src/main/scala/zhttp/service/server/WebSocketUpgrade.scala index be8210aa32..573acb34f7 100644 --- a/zio-http/src/main/scala/zhttp/service/server/WebSocketUpgrade.scala +++ b/zio-http/src/main/scala/zhttp/service/server/WebSocketUpgrade.scala @@ -10,13 +10,13 @@ import zhttp.service.{HttpRuntime, WEB_SOCKET_HANDLER} * Module to switch protocol to websockets */ trait WebSocketUpgrade[R] { self: ChannelHandler => - final def isWebSocket(res: Response[R, Throwable]): Boolean = + final def isWebSocket(res: Response): Boolean = res.status == Status.SWITCHING_PROTOCOLS && res.attribute.socketApp.nonEmpty /** * Checks if the response requires to switch protocol to websocket. Returns true if it can, otherwise returns false */ - final def upgradeToWebSocket(ctx: ChannelHandlerContext, jReq: FullHttpRequest, res: Response[R, Throwable]): Unit = { + final def upgradeToWebSocket(ctx: ChannelHandlerContext, jReq: FullHttpRequest, res: Response): Unit = { val app = res.attribute.socketApp ctx diff --git a/zio-http/src/main/scala/zhttp/socket/Conversion.scala b/zio-http/src/main/scala/zhttp/socket/Conversion.scala deleted file mode 100644 index 8400a70d07..0000000000 --- a/zio-http/src/main/scala/zhttp/socket/Conversion.scala +++ /dev/null @@ -1,9 +0,0 @@ -package zhttp.socket - -import zhttp.http.Response - -trait Conversion { - import scala.language.implicitConversions - - implicit def asResponse[R, E](app: SocketApp[R]): Response[R, E] = app.asResponse -} diff --git a/zio-http/src/main/scala/zhttp/socket/IsWebSocket.scala b/zio-http/src/main/scala/zhttp/socket/IsWebSocket.scala index 177a35a699..a965bf6fe1 100644 --- a/zio-http/src/main/scala/zhttp/socket/IsWebSocket.scala +++ b/zio-http/src/main/scala/zhttp/socket/IsWebSocket.scala @@ -3,10 +3,10 @@ package zhttp.socket sealed trait IsWebSocket[+R, -E, +A, -B] { def apply[R1 >: R, E1 <: E, A1 >: A, B1 <: B]( socket: Socket[R1, E1, A1, B1], - ): Socket[R1, E1, WebSocketFrame, WebSocketFrame] = - socket.asInstanceOf[Socket[R1, E1, WebSocketFrame, WebSocketFrame]] + ): Socket[R1, Throwable, WebSocketFrame, WebSocketFrame] = + socket.asInstanceOf[Socket[R1, Throwable, WebSocketFrame, WebSocketFrame]] } object IsWebSocket extends IsWebSocket[Nothing, Any, WebSocketFrame, WebSocketFrame] { - implicit def webSocketFrame[R, E]: IsWebSocket[R, E, WebSocketFrame, WebSocketFrame] = IsWebSocket + implicit def webSocketFrame[R]: IsWebSocket[R, Throwable, WebSocketFrame, WebSocketFrame] = IsWebSocket } diff --git a/zio-http/src/main/scala/zhttp/socket/Socket.scala b/zio-http/src/main/scala/zhttp/socket/Socket.scala index 321c2ad7de..1a87b64588 100644 --- a/zio-http/src/main/scala/zhttp/socket/Socket.scala +++ b/zio-http/src/main/scala/zhttp/socket/Socket.scala @@ -1,5 +1,6 @@ package zhttp.socket +import zhttp.http.Response import zio.stream.ZStream import zio.{Cause, NeedsEnv, ZIO} @@ -42,6 +43,10 @@ sealed trait Socket[-R, +E, -A, +B] { self => */ def provide(r: R)(implicit env: NeedsEnv[R]): Socket[Any, E, A, B] = Provide(self, r) + def toResponse(implicit ev: IsWebSocket[R, E, A, B]): ZIO[R, Nothing, Response] = toSocketApp.toResponse + + def toSocketApp(implicit ev: IsWebSocket[R, E, A, B]): SocketApp[R] = SocketApp(self) + private[zhttp] def execute(a: A): ZStream[R, E, B] = self(a) } diff --git a/zio-http/src/main/scala/zhttp/socket/SocketApp.scala b/zio-http/src/main/scala/zhttp/socket/SocketApp.scala index 0d04fab060..f609bfffcd 100644 --- a/zio-http/src/main/scala/zhttp/socket/SocketApp.scala +++ b/zio-http/src/main/scala/zhttp/socket/SocketApp.scala @@ -18,11 +18,6 @@ final case class SocketApp[-R]( protocol: SocketProtocol = SocketProtocol.default, ) { self => - /** - * Creates a new WebSocket Response - */ - def asResponse: Response[R, Nothing] = Response.socket(self) - /** * Called when the websocket connection is closed successfully. */ @@ -86,6 +81,11 @@ final case class SocketApp[-R]( close = self.close.map(f => (c: Connection) => f(c).provide(env)), ) + def toResponse: ZIO[R, Nothing, Response] = + ZIO.environment[R].flatMap { env => + Response.fromSocketApp(self.provide(env)) + } + /** * Frame decoder configuration */ @@ -109,8 +109,8 @@ final case class SocketApp[-R]( object SocketApp { type Connection = SocketAddress - def apply[R](socket: Socket[R, Throwable, WebSocketFrame, WebSocketFrame]): SocketApp[R] = - SocketApp(message = Some(socket)) + def apply[R, E, A, B](socket: Socket[R, E, A, B])(implicit ev: IsWebSocket[R, E, A, B]): SocketApp[R] = + SocketApp(message = Some(ev(socket))) private[zhttp] sealed trait Handle[-R] { self => def merge[R1 <: R](other: Handle[R1]): Handle[R1] = { diff --git a/zio-http/src/main/scala/zhttp/socket/package.scala b/zio-http/src/main/scala/zhttp/socket/package.scala index 9c83aaea0b..cf40dd6638 100644 --- a/zio-http/src/main/scala/zhttp/socket/package.scala +++ b/zio-http/src/main/scala/zhttp/socket/package.scala @@ -1,3 +1,3 @@ package zhttp -package object socket extends Conversion {} +package object socket {} diff --git a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala index 2aea613398..3878e24d86 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala @@ -114,7 +114,7 @@ object HttpGen { data <- HttpGen.httpData(Gen.listOf(Gen.alphaNumericString)) } yield Request(method, url, headers, None, data) - def response[R](gContent: Gen[R, List[String]]): Gen[Random with Sized with R, Response[Any, Nothing]] = { + def response[R](gContent: Gen[R, List[String]]): Gen[Random with Sized with R, Response] = { for { content <- HttpGen.httpData(gContent) headers <- HttpGen.header.map(Headers(_)) diff --git a/zio-http/src/test/scala/zhttp/middleware/MiddlewareSpec.scala b/zio-http/src/test/scala/zhttp/middleware/MiddlewareSpec.scala index 6b2b2a002d..b33e5e8db2 100644 --- a/zio-http/src/test/scala/zhttp/middleware/MiddlewareSpec.scala +++ b/zio-http/src/test/scala/zhttp/middleware/MiddlewareSpec.scala @@ -226,7 +226,7 @@ object MiddlewareSpec extends DefaultRunnableSpec with HttpAppTestExtensions { private def condM(flg: Boolean) = (_: Any, _: Any, _: Any) => UIO(flg) - private def run[R, E](app: HttpApp[R, E]): ZIO[TestClock with R, Option[E], Response[R, E]] = { + private def run[R, E](app: HttpApp[R, E]): ZIO[TestClock with R, Option[E], Response] = { for { fib <- app { Request(url = URL(!! / "health")) }.fork _ <- TestClock.adjust(10 seconds) diff --git a/zio-http/src/test/scala/zhttp/service/ServerSpec.scala b/zio-http/src/test/scala/zhttp/service/ServerSpec.scala index 777d897ab0..13ea4e5a7d 100644 --- a/zio-http/src/test/scala/zhttp/service/ServerSpec.scala +++ b/zio-http/src/test/scala/zhttp/service/ServerSpec.scala @@ -199,7 +199,7 @@ object ServerSpec extends HttpRunnableSpec { val expected = (0 to size) map (_ => Status.OK) for { response <- Response.text("abc").freeze - actual <- ZIO.foreachPar(0 to size)(_ => Http.response(response).requestStatus()) + actual <- ZIO.foreachPar(0 to size)(_ => (Http.response(response): HttpApp[Any, Throwable]).requestStatus()) } yield assert(actual)(equalTo(expected)) } + testM("update after cache") { diff --git a/zio-http/src/test/scala/zhttp/service/WebSocketServerSpec.scala b/zio-http/src/test/scala/zhttp/service/WebSocketServerSpec.scala index 028fefa306..916a964fc2 100644 --- a/zio-http/src/test/scala/zhttp/service/WebSocketServerSpec.scala +++ b/zio-http/src/test/scala/zhttp/service/WebSocketServerSpec.scala @@ -4,8 +4,7 @@ import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend import zhttp.http._ import zhttp.internal.{DynamicServer, HttpRunnableSpec} import zhttp.service.server._ -import zhttp.socket.{Socket, SocketApp, WebSocketFrame} -import zio._ +import zhttp.socket.{Socket, WebSocketFrame} import zio.duration._ import zio.test.Assertion.equalTo import zio.test.TestAspect.timeout @@ -20,8 +19,8 @@ object WebSocketServerSpec extends HttpRunnableSpec { def websocketSpec = suite("WebSocket Server") { suite("connections") { testM("Multiple websocket upgrades") { - val socketApp = SocketApp(Socket.succeed(WebSocketFrame.text("BAR"))) - val app = Http.fromEffect(ZIO(Response.socket(socketApp))) + val response = Socket.succeed(WebSocketFrame.text("BAR")).toResponse + val app = Http.fromEffect(response) assertM(app.webSocketStatusCode(!! / "subscriptions").repeatN(1024))(equalTo(101)) } } From 7fa4ccaabc26b8499e63fd30f917155c9ce87987 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 6 Jan 2022 17:40:31 +0530 Subject: [PATCH 2/2] chore: self review --- zio-http/src/main/scala/zhttp/http/Http.scala | 2 +- zio-http/src/main/scala/zhttp/http/Patch.scala | 8 ++++---- zio-http/src/main/scala/zhttp/socket/Socket.scala | 6 ++++++ zio-http/src/main/scala/zhttp/socket/SocketApp.scala | 3 +++ zio-http/src/test/scala/zhttp/service/ServerSpec.scala | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index 1b5b7ededc..4686fac9b6 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -532,7 +532,7 @@ object Http { /** * Creates an Http app which always responds with the same value. */ - def response[R, E](response: Response): HttpApp[R, E] = Http.succeed(response) + def response(response: Response): HttpApp[Any, Nothing] = Http.succeed(response) /** * Converts a ZIO to an Http app type diff --git a/zio-http/src/main/scala/zhttp/http/Patch.scala b/zio-http/src/main/scala/zhttp/http/Patch.scala index 7ad0b0ec00..7adff2bf1e 100644 --- a/zio-http/src/main/scala/zhttp/http/Patch.scala +++ b/zio-http/src/main/scala/zhttp/http/Patch.scala @@ -6,17 +6,17 @@ import scala.annotation.tailrec * Models the set of operations that one would want to apply on a Response. */ sealed trait Patch { self => - def ++(that: Patch): Patch = Patch.Combine(self, that) - def apply[R, E](res: Response): Response = { + def ++(that: Patch): Patch = Patch.Combine(self, that) + def apply(res: Response): Response = { @tailrec - def loop[R1, E1](res: Response, patch: Patch): Response = + def loop(res: Response, patch: Patch): Response = patch match { case Patch.Empty => res case Patch.AddHeaders(headers) => res.addHeaders(headers) case Patch.RemoveHeaders(headers) => res.removeHeaders(headers) case Patch.SetStatus(status) => res.setStatus(status) - case Patch.Combine(self, other) => loop[R1, E1](self(res), other) + case Patch.Combine(self, other) => loop(self(res), other) } loop(res, self) diff --git a/zio-http/src/main/scala/zhttp/socket/Socket.scala b/zio-http/src/main/scala/zhttp/socket/Socket.scala index 1a87b64588..205a077fa8 100644 --- a/zio-http/src/main/scala/zhttp/socket/Socket.scala +++ b/zio-http/src/main/scala/zhttp/socket/Socket.scala @@ -43,8 +43,14 @@ sealed trait Socket[-R, +E, -A, +B] { self => */ def provide(r: R)(implicit env: NeedsEnv[R]): Socket[Any, E, A, B] = Provide(self, r) + /** + * Creates a response from the socket. + */ def toResponse(implicit ev: IsWebSocket[R, E, A, B]): ZIO[R, Nothing, Response] = toSocketApp.toResponse + /** + * Creates a socket application from the socket. + */ def toSocketApp(implicit ev: IsWebSocket[R, E, A, B]): SocketApp[R] = SocketApp(self) private[zhttp] def execute(a: A): ZStream[R, E, B] = self(a) diff --git a/zio-http/src/main/scala/zhttp/socket/SocketApp.scala b/zio-http/src/main/scala/zhttp/socket/SocketApp.scala index f609bfffcd..43dea50673 100644 --- a/zio-http/src/main/scala/zhttp/socket/SocketApp.scala +++ b/zio-http/src/main/scala/zhttp/socket/SocketApp.scala @@ -81,6 +81,9 @@ final case class SocketApp[-R]( close = self.close.map(f => (c: Connection) => f(c).provide(env)), ) + /** + * Creates a new response from the socket app. + */ def toResponse: ZIO[R, Nothing, Response] = ZIO.environment[R].flatMap { env => Response.fromSocketApp(self.provide(env)) diff --git a/zio-http/src/test/scala/zhttp/service/ServerSpec.scala b/zio-http/src/test/scala/zhttp/service/ServerSpec.scala index 13ea4e5a7d..777d897ab0 100644 --- a/zio-http/src/test/scala/zhttp/service/ServerSpec.scala +++ b/zio-http/src/test/scala/zhttp/service/ServerSpec.scala @@ -199,7 +199,7 @@ object ServerSpec extends HttpRunnableSpec { val expected = (0 to size) map (_ => Status.OK) for { response <- Response.text("abc").freeze - actual <- ZIO.foreachPar(0 to size)(_ => (Http.response(response): HttpApp[Any, Throwable]).requestStatus()) + actual <- ZIO.foreachPar(0 to size)(_ => Http.response(response).requestStatus()) } yield assert(actual)(equalTo(expected)) } + testM("update after cache") {