Permalink
Browse files

! httpx: flexibilize RequestBuilding and ResponseTransformation by ge…

…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 14, 2013
1 parent f896993 commit f5997f8688bab0a4c13c5645a1790f5138fb90f5
@@ -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) {
@@ -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
@@ -43,7 +43,6 @@ trait RequestBuilding {
case Left(error) throw error
}
})
- }
}
val Get = new RequestBuilder(GET)
@@ -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 :: _)
@@ -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
@@ -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)
+ }
+}
@@ -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)))

0 comments on commit f5997f8

Please sign in to comment.