Skip to content

Commit

Permalink
Refactor: Remove type params from Response (#772)
Browse files Browse the repository at this point in the history
* refactor: remove type-params from Response

* chore: self review
  • Loading branch information
Tushar Mathur authored Jan 6, 2022
1 parent 1203052 commit 2d26f13
Show file tree
Hide file tree
Showing 22 changed files with 126 additions and 109 deletions.
4 changes: 2 additions & 2 deletions example/src/main/scala/example/PlainTextBenchmarkServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) ++
Expand Down
10 changes: 5 additions & 5 deletions example/src/main/scala/example/WebSocketAdvanced.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] =
Expand Down
8 changes: 4 additions & 4 deletions example/src/main/scala/example/WebSocketEcho.scala
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -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] =
Expand Down
14 changes: 7 additions & 7 deletions zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 =>
Expand Down
4 changes: 2 additions & 2 deletions zio-http/src/main/scala/zhttp/http/CanBeSilenced.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
Expand Down
8 changes: 4 additions & 4 deletions zio-http/src/main/scala/zhttp/http/Http.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(response: Response): HttpApp[Any, Nothing] = 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.
Expand Down
2 changes: 1 addition & 1 deletion zio-http/src/main/scala/zhttp/http/HttpError.scala
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion zio-http/src/main/scala/zhttp/http/Middleware.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
}
}

Expand Down
8 changes: 4 additions & 4 deletions zio-http/src/main/scala/zhttp/http/Patch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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[R, E]): Response[R, E] = {
def ++(that: Patch): Patch = Patch.Combine(self, that)
def apply(res: Response): Response = {

@tailrec
def loop[R1, E1](res: Response[R1, E1], patch: Patch): Response[R1, E1] =
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)
Expand Down
97 changes: 56 additions & 41 deletions zio-http/src/main/scala/zhttp/http/Response.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))

/**
Expand All @@ -33,33 +33,38 @@ 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

/**
* 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
Expand Down Expand Up @@ -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(
Expand All @@ -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("<!DOCTYPE html>" + data.encode),
headers = Headers(HeaderNames.contentType, HeaderValues.textHtml),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -197,27 +212,27 @@ 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 {

/**
* Helper to create an empty HttpData
*/
def empty: Attribute[Any, Nothing] = Attribute()
def empty: Attribute = Attribute()
}
}
Loading

0 comments on commit 2d26f13

Please sign in to comment.