Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Commit

Permalink
! httpx: flexibilize RequestBuilding and ResponseTransformation by ge…
Browse files Browse the repository at this point in the history
…neralizing the `~>` operator

Even though we do change the public API with this it should only in rare cases lead to the compiler errors. By factoring out all the logic for the `~>` operator into a dedicated `TransformerPipelineSupport` trait + object the request building side of a pipeline now also benefits from the full flexibility the operator (as a generic function "concatenator") brings. For example, you can now chain a function `HttpRequest => Future[HttpRequest]` into the pipeline and everything will work as expected.
  • Loading branch information
sirthias committed Jun 18, 2013
1 parent f896993 commit f5997f8
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 58 deletions.
20 changes: 6 additions & 14 deletions spray-httpx/src/main/scala/spray/httpx/RequestBuilding.scala
Expand Up @@ -24,7 +24,7 @@ import spray.http._
import HttpMethods._
import HttpHeaders._

trait RequestBuilding {
trait RequestBuilding extends TransformerPipelineSupport {
type RequestTransformer = HttpRequest HttpRequest

class RequestBuilder(val method: HttpMethod) {
Expand All @@ -34,7 +34,7 @@ trait RequestBuilding {
def apply[T: Marshaller](uri: String, content: Option[T]): HttpRequest = apply(Uri(uri), content)
def apply(uri: Uri): HttpRequest = apply[String](uri, None)
def apply[T: Marshaller](uri: Uri, content: T): HttpRequest = apply(uri, Some(content))
def apply[T: Marshaller](uri: Uri, content: Option[T]): HttpRequest = {
def apply[T: Marshaller](uri: Uri, content: Option[T]): HttpRequest =
HttpRequest(method, uri,
entity = content match {
case None EmptyEntity
Expand All @@ -43,7 +43,6 @@ trait RequestBuilding {
case Left(error) throw error
}
})
}
}

val Get = new RequestBuilder(GET)
Expand All @@ -53,6 +52,7 @@ trait RequestBuilding {
val Delete = new RequestBuilder(DELETE)
val Options = new RequestBuilder(OPTIONS)
val Head = new RequestBuilder(HEAD)

def encode(encoder: Encoder): RequestTransformer = encoder.encode(_)

def addHeader(header: HttpHeader): RequestTransformer = _.mapHeaders(header :: _)
Expand All @@ -68,19 +68,11 @@ trait RequestBuilding {

def addCredentials(credentials: HttpCredentials) = addHeader(HttpHeaders.Authorization(credentials))

def logRequest(log: LoggingAdapter): HttpRequest HttpRequest =
logRequest { request log.debug(request.toString) }
def logRequest(log: LoggingAdapter) = logValue[HttpRequest](log)

def logRequest(logFun: HttpRequest Unit): HttpRequest HttpRequest = { request
logFun(request)
request
}
def logRequest(logFun: HttpRequest Unit) = logValue[HttpRequest](logFun)

implicit def request2TransformableHttpRequest(request: HttpRequest) = new TransformableHttpRequest(request)
class TransformableHttpRequest(request: HttpRequest) {
def ~>[T](f: HttpRequest T) = f(request)
def ~>(header: HttpHeader) = addHeader(header)(request)
}
implicit def header2AddHeader(header: HttpHeader): RequestTransformer = addHeader(header)
}

object RequestBuilding extends RequestBuilding
57 changes: 13 additions & 44 deletions spray-httpx/src/main/scala/spray/httpx/ResponseTransformation.scala
Expand Up @@ -16,64 +16,33 @@

package spray.httpx

import scala.concurrent.{ ExecutionContext, Future }
import akka.event.LoggingAdapter
import spray.httpx.unmarshalling._
import spray.httpx.encoding.Decoder
import spray.http._

trait ResponseTransformation {
trait ResponseTransformation extends TransformerPipelineSupport {
type ResponseTransformer = HttpResponse HttpResponse

def decode(decoder: Decoder): ResponseTransformer = { response
if (response.encoding == decoder.encoding) decoder.decode(response) else response
}
def decode(decoder: Decoder): ResponseTransformer =
response if (response.encoding == decoder.encoding) decoder.decode(response) else response

def unmarshal[T: Unmarshaller]: HttpResponse T = { response
if (response.status.isSuccess)
response.entity.as[T] match {
case Right(value) value
case Left(error) throw new PipelineException(error.toString)
}
else throw new UnsuccessfulResponseException(response)
}
def unmarshal[T: Unmarshaller]: HttpResponse T =
response
if (response.status.isSuccess)
response.entity.as[T] match {
case Right(value) value
case Left(error) throw new PipelineException(error.toString)
}
else throw new UnsuccessfulResponseException(response)

def logResponse(log: LoggingAdapter): HttpResponse HttpResponse =
logResponse { response log.debug(response.toString) }
def logResponse(log: LoggingAdapter) = logValue[HttpResponse](log)

def logResponse(logFun: HttpResponse Unit): HttpResponse HttpResponse = { response
logFun(response)
response
}

implicit def pimpWithResponseTransformation[A, B](f: A B) = new PimpedResponseTransformer(f)
class PimpedResponseTransformer[A, B](f: A B) extends (A B) {
def apply(input: A) = f(input)
def ~>[AA, BB, R](g: AA BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
new PimpedResponseTransformer[A, R](aux(f, g))
}
def logResponse(logFun: HttpResponse Unit) = logValue[HttpResponse](logFun)
}

object ResponseTransformation extends ResponseTransformation

trait TransformerAux[A, B, AA, BB, R] {
def apply(f: A B, g: AA BB): A R
}

object TransformerAux {
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
def apply(f: A B, g: B C): A C = f andThen g
}
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, C, Future[C]] {
def apply(f: A Future[B], g: B C): A Future[C] = f(_).map(g)
}
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
def apply(f: A Future[B], g: B Future[C]): A Future[C] = f(_).flatMap(g)
}
}

class PipelineException(message: String, cause: Throwable = null) extends RuntimeException(message, cause)
class UnsuccessfulResponseException(val response: HttpResponse) extends RuntimeException(s"Status: ${response.status}\n" +
s"Body: ${if (response.entity.buffer.length < 1024) response.entity.asString else response.entity.buffer.length + " bytes"}")
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2011-2013 spray.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 spray.httpx

import scala.concurrent.{ ExecutionContext, Future }
import akka.event.{ Logging, LoggingAdapter }

trait TransformerPipelineSupport {

def logValue[T](log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel): T T =
logValue { value log.log(level, value.toString) }

def logValue[T](logFun: T Unit): T T = { response
logFun(response)
response
}

implicit class WithTransformation[A](value: A) {
def ~>[B](f: A B): B = f(value)
}

implicit class WithTransformerConcatenation[A, B](f: A B) extends (A B) {
def apply(input: A) = f(input)
def ~>[AA, BB, R](g: AA BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
new WithTransformerConcatenation[A, R](aux(f, g))
}
}

object TransformerPipelineSupport extends TransformerPipelineSupport

trait TransformerAux[A, B, AA, BB, R] {
def apply(f: A B, g: AA BB): A R
}

object TransformerAux {
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
def apply(f: A B, g: B C): A C = f andThen g
}
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, C, Future[C]] {
def apply(f: A Future[B], g: B C): A Future[C] = f(_).map(g)
}
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
def apply(f: A Future[B], g: B Future[C]): A Future[C] = f(_).flatMap(g)
}
}
Expand Up @@ -38,6 +38,10 @@ class RequestBuildingSpec extends Specification with RequestBuilding {
HttpRequest(headers = List(Authorization(BasicHttpCredentials("bla")), RawHeader("X-Yeah", "Naah")))
}

"support adding headers directly without explicit `addHeader` transformer" >> {
Get() ~> RawHeader("X-Yeah", "Naah") === HttpRequest(headers = List(RawHeader("X-Yeah", "Naah")))
}

"provide the ability to add generic Authorization credentials to the request" >> {
val creds = GenericHttpCredentials("OAuth", Map("oauth_version" -> "1.0"))
Get() ~> addCredentials(creds) === HttpRequest(headers = List(Authorization(creds)))
Expand Down

0 comments on commit f5997f8

Please sign in to comment.