All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smithy4s.http.HttpRequest.scala Maven / Gradle / Ivy

There is a newer version: 0.19.0-41-91762fb
Show newest version
/*
 *  Copyright 2021-2024 Disney Streaming
 *
 *  Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     https://disneystreaming.github.io/TOST-1.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package smithy4s.http

import smithy4s.kinds._
import smithy4s.capability.Covariant
import smithy4s.schema._
import smithy4s.codecs.{Decoder => GenericDecoder}
import smithy4s.capability.MonadThrowLike

final case class HttpRequest[+A](
    method: HttpMethod,
    uri: HttpUri,
    headers: Map[CaseInsensitive, Seq[String]],
    body: A
) {
  def map[B](f: A => B): HttpRequest[B] =
    HttpRequest(method, uri, headers, f(body))

  def toMetadata: Metadata = Metadata(
    path = uri.pathParams.getOrElse(Map.empty),
    query = uri.queryParams,
    headers = headers
  )

  def addHeaders(headers: Map[CaseInsensitive, Seq[String]]): HttpRequest[A] =
    this.copy(headers = this.headers ++ headers)

  def withContentType(contentType: String): HttpRequest[A] =
    this.copy(headers =
      this.headers + (CaseInsensitive("Content-Type") -> Seq(contentType))
    )
}

object HttpRequest {
  private[http] type Writer[Body, A] =
    smithy4s.codecs.Writer[HttpRequest[Body], A]
  private[http] type Decoder[F[_], Body, A] =
    smithy4s.codecs.Decoder[F, HttpRequest[Body], A]

  implicit val reqCovariant: Covariant[HttpRequest] =
    new Covariant[HttpRequest] {
      def map[A, B](req: HttpRequest[A])(f: A => B): HttpRequest[B] = req.map(f)
    }

  private[http] object Writer {

    def restSchemaCompiler[Body](
        metadataEncoders: CachedSchemaCompiler[Metadata.Encoder],
        bodyEncoders: CachedSchemaCompiler[Writer[Body, *]],
        writeEmptyStructs: Schema[_] => Boolean
    ): CachedSchemaCompiler[Writer[Body, *]] = {
      val metadataCompiler = metadataEncoders.mapK(
        smithy4s.codecs.Encoder.pipeToWriterK(metadataWriter[Body])
      )
      HttpRestSchema.combineWriterCompilers(
        metadataCompiler,
        bodyEncoders,
        writeEmptyStructs
      )
    }

    def fromHttpEndpoint[Body, I](
        httpEndpoint: HttpEndpoint[I]
    ): Writer[Body, I] = new Writer[Body, I] {
      def write(request: HttpRequest[Body], input: I): HttpRequest[Body] = {
        val path = httpEndpoint.path(input)
        val staticQueries = httpEndpoint.staticQueryParams
        val oldUri = request.uri
        val newUri =
          oldUri.copy(path = oldUri.path ++ path, queryParams = staticQueries)
        val method = httpEndpoint.method
        request.copy(method = method, uri = newUri)
      }
    }

    private def metadataWriter[Body]: Writer[Body, Metadata] = {
      (req: HttpRequest[Body], meta: Metadata) =>
        val oldUri = req.uri
        val newUri =
          oldUri.copy(queryParams = oldUri.queryParams ++ meta.query)
        req.addHeaders(meta.headers).copy(uri = newUri)
    }

    private[http] def hostPrefix[Body, I](
        httpEndpoint: OperationSchema[I, _, _, _, _]
    ): Writer[Body, I] = {
      HttpHostPrefix(httpEndpoint) match {
        case Some(prefixEncoder) =>
          new Writer[Body, I] {
            def write(
                request: HttpRequest[Body],
                input: I
            ): HttpRequest[Body] = {
              val hostPrefix = prefixEncoder.write(List.empty, input)
              val oldUri = request.uri
              val newUri =
                oldUri.copy(host = s"${hostPrefix.mkString}${oldUri.host}")
              request.copy(uri = newUri)
            }
          }
        case None =>
          (r, _) => r
      }
    }

  }

  object Decoder {

    def restSchemaCompiler[F[_]: MonadThrowLike, Body](
        metadataDecoders: CachedSchemaCompiler[Metadata.Decoder],
        bodyDecoders: CachedSchemaCompiler[GenericDecoder[F, Body, *]],
        drainBody: Option[HttpRequest[Body] => F[Unit]]
    ): CachedSchemaCompiler[Decoder[F, Body, *]] = {
      restSchemaCompilerAux(
        metadataDecoders,
        bodyDecoders.mapK { extractBody[F, Body] },
        drainBody.getOrElse(_ => MonadThrowLike[F].pure(()))
      )
    }

    private[smithy4s] def restSchemaCompilerAux[F[_]: MonadThrowLike, Body](
        metadataDecoders: CachedSchemaCompiler[Metadata.Decoder],
        responseDecoderCompiler: CachedSchemaCompiler[Decoder[F, Body, *]],
        drainBody: HttpRequest[Body] => F[Unit]
    ): CachedSchemaCompiler[Decoder[F, Body, *]] = {
      val restMetadataCompiler: CachedSchemaCompiler[Decoder[F, Body, *]] =
        metadataDecoders.mapK(
          extractMetadata[F](MonadThrowLike.liftEitherK[F, MetadataError])
        )

      HttpRestSchema.combineDecoderCompilers[F, HttpRequest[Body]](
        restMetadataCompiler,
        responseDecoderCompiler,
        drainBody
      )
    }
  }

  private def extractMetadata[F[_]](
      liftToF: PolyFunction[Either[MetadataError, *], F]
  ): PolyFunction[Metadata.Decoder, Decoder[F, Any, *]] =
    GenericDecoder
      .in[Either[MetadataError, *]]
      .composeK((_: HttpRequest[Any]).toMetadata)
      .andThen(GenericDecoder.of[HttpRequest[Any]].liftPolyFunction(liftToF))

  private[smithy4s] def extractBody[F[_], Body]
      : PolyFunction[GenericDecoder[F, Body, *], Decoder[F, Body, *]] =
    GenericDecoder.in[F].composeK(_.body)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy