Skip to content
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

Refactor: Remove type params from Response #772

Merged
merged 2 commits into from
Jan 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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