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

Make Http response more composable #179

Closed
ShrutiVerma97 opened this issue May 5, 2021 · 0 comments
Closed

Make Http response more composable #179

ShrutiVerma97 opened this issue May 5, 2021 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@ShrutiVerma97
Copy link
Contributor

ShrutiVerma97 commented May 5, 2021

package zhttp.http

import io.netty.handler.codec.http.{
  DefaultHttpResponse => JDefaultHttpResponse,
  HttpResponse => JHttpResponse,
  HttpVersion => JHttpVersion,
}
import zhttp.http.Status.OK

import scala.annotation.{implicitAmbiguous, implicitNotFound, unused}
import scala.language.implicitConversions

sealed trait HttpResponseBuilder[+S, +A] { self =>
  import HttpResponseBuilder._

  def ++[S1 >: S, S2, S3, A1 >: A, A2, A3](other: HttpResponseBuilder[S2, A2])(implicit
    @unused @implicitNotFound("Status is already set once")
    s: CanCombine[S1, S2, S3],
    @unused @implicitNotFound("Content is already set once")
    a: CanCombine[A1, A2, A3],
  ): HttpResponseBuilder[S3, A3] =
    HttpResponseBuilder.Combine(
      self.asInstanceOf[HttpResponseBuilder[S3, A3]],
      other.asInstanceOf[HttpResponseBuilder[S3, A3]],
    )

  def widen[S1, A1](implicit evS: S <:< S1, evA: A <:< A1): HttpResponseBuilder[S1, A1] =
    self.asInstanceOf[HttpResponseBuilder[S1, A1]]

  private[zhttp] def jHttpResponse[S1 >: S](implicit ev: S1 <:< Status): JHttpResponse = {
    val jResponse: JHttpResponse = new JDefaultHttpResponse(JHttpVersion.HTTP_1_1, OK.toJHttpStatus)

    def loop(response: HttpResponseBuilder[Status, A]): Unit = {
      response match {
        case HttpResponseStatus(status) => jResponse.setStatus(status.toJHttpStatus)
        case HttpResponseHeader(header) => jResponse.headers().set(header.name, header.value)
        case Combine(a, b)              => loop(a); loop(b)
        case _                          => ()
      }
      ()
    }

    loop(self.widen)
    jResponse
  }

  private[zhttp] def content[A1 >: A](implicit @unused ev: HasContent[A1]): A1 = {
    val nullA = null.asInstanceOf[A1]
    def loop(response: HttpResponseBuilder[S, A1]): A1 = {
      response match {
        case Combine(a, b)                =>
          val a0 = loop(a)
          if (a0 == null) loop(b) else a0
        case HttpResponseContent(content) => content
        case _                            => nullA
      }
    }
    loop(self)
  }
}

object HttpResponseBuilder {
  private final case class HttpResponseStatus[S](status: S)   extends HttpResponseBuilder[S, Nothing]
  private final case class HttpResponseHeader(header: Header) extends HttpResponseBuilder[Nothing, Nothing]
  private final case class HttpResponseContent[A](data: A)    extends HttpResponseBuilder[Nothing, A]
  private final case class Combine[S, A](a: HttpResponseBuilder[S, A], b: HttpResponseBuilder[S, A])
      extends HttpResponseBuilder[S, A]

  sealed trait CanCombine[X, Y, A]

  object CanCombine {
    implicit def combineL[A]: CanCombine[A, Nothing, A]                = null
    implicit def combineR[A]: CanCombine[Nothing, A, A]                = null
    implicit def combineNothing: CanCombine[Nothing, Nothing, Nothing] = null
  }

  @implicitNotFound("Response doesn't have status set")
  sealed trait HasStatus[S]
  implicit object HasStatus extends HasStatus[Status]

  @implicitAmbiguous("Response doesn't have status set")
  implicit object HasNoStatus0 extends HasStatus[Nothing]
  implicit object HasNoStatus1 extends HasStatus[Nothing]

  trait HasContent[A]
  object HasContent {
    implicit object NoContent0 extends HasContent[Nothing]
    implicit def hasContent[A]: HasContent[A] = null.asInstanceOf[HasContent[A]]
  }

  implicit def status(status: Status): HttpResponseBuilder[Status, Nothing] =
    HttpResponseStatus(status)

  implicit def header(header: Header): HttpResponseBuilder[Nothing, Nothing] =
    HttpResponseHeader(header)

  implicit def content[R, E](data: HttpData[R, E]): HttpResponseBuilder[Nothing, HttpData[R, E]] =
    HttpResponseContent(data)

  implicit def asResponse[S, A, R, E](self: HttpResponseBuilder[S, A])(implicit
    evS: S =:= Status,
    @unused evStatus: HasStatus[S],
    evA: HasContent[A],
    evD: A =:= HttpData[R, E],
  ): Response[R, E] = Response.FromResponseBuilder(self.widen)
}
@ShrutiVerma97 ShrutiVerma97 added the enhancement New feature or request label May 5, 2021
@ShrutiVerma97 ShrutiVerma97 self-assigned this May 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants