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

Commit f5997f8

Browse files
committed
! httpx: flexibilize RequestBuilding and ResponseTransformation by generalizing 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.
1 parent f896993 commit f5997f8

File tree

4 files changed

+84
-58
lines changed

4 files changed

+84
-58
lines changed

spray-httpx/src/main/scala/spray/httpx/RequestBuilding.scala

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import spray.http._
2424
import HttpMethods._
2525
import HttpHeaders._
2626

27-
trait RequestBuilding {
27+
trait RequestBuilding extends TransformerPipelineSupport {
2828
type RequestTransformer = HttpRequest HttpRequest
2929

3030
class RequestBuilder(val method: HttpMethod) {
@@ -34,7 +34,7 @@ trait RequestBuilding {
3434
def apply[T: Marshaller](uri: String, content: Option[T]): HttpRequest = apply(Uri(uri), content)
3535
def apply(uri: Uri): HttpRequest = apply[String](uri, None)
3636
def apply[T: Marshaller](uri: Uri, content: T): HttpRequest = apply(uri, Some(content))
37-
def apply[T: Marshaller](uri: Uri, content: Option[T]): HttpRequest = {
37+
def apply[T: Marshaller](uri: Uri, content: Option[T]): HttpRequest =
3838
HttpRequest(method, uri,
3939
entity = content match {
4040
case None EmptyEntity
@@ -43,7 +43,6 @@ trait RequestBuilding {
4343
case Left(error) throw error
4444
}
4545
})
46-
}
4746
}
4847

4948
val Get = new RequestBuilder(GET)
@@ -53,6 +52,7 @@ trait RequestBuilding {
5352
val Delete = new RequestBuilder(DELETE)
5453
val Options = new RequestBuilder(OPTIONS)
5554
val Head = new RequestBuilder(HEAD)
55+
5656
def encode(encoder: Encoder): RequestTransformer = encoder.encode(_)
5757

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

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

71-
def logRequest(log: LoggingAdapter): HttpRequest HttpRequest =
72-
logRequest { request log.debug(request.toString) }
71+
def logRequest(log: LoggingAdapter) = logValue[HttpRequest](log)
7372

74-
def logRequest(logFun: HttpRequest Unit): HttpRequest HttpRequest = { request
75-
logFun(request)
76-
request
77-
}
73+
def logRequest(logFun: HttpRequest Unit) = logValue[HttpRequest](logFun)
7874

79-
implicit def request2TransformableHttpRequest(request: HttpRequest) = new TransformableHttpRequest(request)
80-
class TransformableHttpRequest(request: HttpRequest) {
81-
def ~>[T](f: HttpRequest T) = f(request)
82-
def ~>(header: HttpHeader) = addHeader(header)(request)
83-
}
75+
implicit def header2AddHeader(header: HttpHeader): RequestTransformer = addHeader(header)
8476
}
8577

8678
object RequestBuilding extends RequestBuilding

spray-httpx/src/main/scala/spray/httpx/ResponseTransformation.scala

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,64 +16,33 @@
1616

1717
package spray.httpx
1818

19-
import scala.concurrent.{ ExecutionContext, Future }
2019
import akka.event.LoggingAdapter
2120
import spray.httpx.unmarshalling._
2221
import spray.httpx.encoding.Decoder
2322
import spray.http._
2423

25-
trait ResponseTransformation {
24+
trait ResponseTransformation extends TransformerPipelineSupport {
2625
type ResponseTransformer = HttpResponse HttpResponse
2726

28-
def decode(decoder: Decoder): ResponseTransformer = { response
29-
if (response.encoding == decoder.encoding) decoder.decode(response) else response
30-
}
27+
def decode(decoder: Decoder): ResponseTransformer =
28+
response if (response.encoding == decoder.encoding) decoder.decode(response) else response
3129

32-
def unmarshal[T: Unmarshaller]: HttpResponse T = { response
33-
if (response.status.isSuccess)
34-
response.entity.as[T] match {
35-
case Right(value) value
36-
case Left(error) throw new PipelineException(error.toString)
37-
}
38-
else throw new UnsuccessfulResponseException(response)
39-
}
30+
def unmarshal[T: Unmarshaller]: HttpResponse T =
31+
response
32+
if (response.status.isSuccess)
33+
response.entity.as[T] match {
34+
case Right(value) value
35+
case Left(error) throw new PipelineException(error.toString)
36+
}
37+
else throw new UnsuccessfulResponseException(response)
4038

41-
def logResponse(log: LoggingAdapter): HttpResponse HttpResponse =
42-
logResponse { response log.debug(response.toString) }
39+
def logResponse(log: LoggingAdapter) = logValue[HttpResponse](log)
4340

44-
def logResponse(logFun: HttpResponse Unit): HttpResponse HttpResponse = { response
45-
logFun(response)
46-
response
47-
}
48-
49-
implicit def pimpWithResponseTransformation[A, B](f: A B) = new PimpedResponseTransformer(f)
50-
class PimpedResponseTransformer[A, B](f: A B) extends (A B) {
51-
def apply(input: A) = f(input)
52-
def ~>[AA, BB, R](g: AA BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
53-
new PimpedResponseTransformer[A, R](aux(f, g))
54-
}
41+
def logResponse(logFun: HttpResponse Unit) = logValue[HttpResponse](logFun)
5542
}
5643

5744
object ResponseTransformation extends ResponseTransformation
5845

59-
trait TransformerAux[A, B, AA, BB, R] {
60-
def apply(f: A B, g: AA BB): A R
61-
}
62-
63-
object TransformerAux {
64-
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
65-
def apply(f: A B, g: B C): A C = f andThen g
66-
}
67-
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
68-
new TransformerAux[A, Future[B], B, C, Future[C]] {
69-
def apply(f: A Future[B], g: B C): A Future[C] = f(_).map(g)
70-
}
71-
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
72-
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
73-
def apply(f: A Future[B], g: B Future[C]): A Future[C] = f(_).flatMap(g)
74-
}
75-
}
76-
7746
class PipelineException(message: String, cause: Throwable = null) extends RuntimeException(message, cause)
7847
class UnsuccessfulResponseException(val response: HttpResponse) extends RuntimeException(s"Status: ${response.status}\n" +
7948
s"Body: ${if (response.entity.buffer.length < 1024) response.entity.asString else response.entity.buffer.length + " bytes"}")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (C) 2011-2013 spray.io
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package spray.httpx
18+
19+
import scala.concurrent.{ ExecutionContext, Future }
20+
import akka.event.{ Logging, LoggingAdapter }
21+
22+
trait TransformerPipelineSupport {
23+
24+
def logValue[T](log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel): T T =
25+
logValue { value log.log(level, value.toString) }
26+
27+
def logValue[T](logFun: T Unit): T T = { response
28+
logFun(response)
29+
response
30+
}
31+
32+
implicit class WithTransformation[A](value: A) {
33+
def ~>[B](f: A B): B = f(value)
34+
}
35+
36+
implicit class WithTransformerConcatenation[A, B](f: A B) extends (A B) {
37+
def apply(input: A) = f(input)
38+
def ~>[AA, BB, R](g: AA BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
39+
new WithTransformerConcatenation[A, R](aux(f, g))
40+
}
41+
}
42+
43+
object TransformerPipelineSupport extends TransformerPipelineSupport
44+
45+
trait TransformerAux[A, B, AA, BB, R] {
46+
def apply(f: A B, g: AA BB): A R
47+
}
48+
49+
object TransformerAux {
50+
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
51+
def apply(f: A B, g: B C): A C = f andThen g
52+
}
53+
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
54+
new TransformerAux[A, Future[B], B, C, Future[C]] {
55+
def apply(f: A Future[B], g: B C): A Future[C] = f(_).map(g)
56+
}
57+
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
58+
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
59+
def apply(f: A Future[B], g: B Future[C]): A Future[C] = f(_).flatMap(g)
60+
}
61+
}

spray-httpx/src/test/scala/spray/httpx/RequestBuildingSpec.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class RequestBuildingSpec extends Specification with RequestBuilding {
3838
HttpRequest(headers = List(Authorization(BasicHttpCredentials("bla")), RawHeader("X-Yeah", "Naah")))
3939
}
4040

41+
"support adding headers directly without explicit `addHeader` transformer" >> {
42+
Get() ~> RawHeader("X-Yeah", "Naah") === HttpRequest(headers = List(RawHeader("X-Yeah", "Naah")))
43+
}
44+
4145
"provide the ability to add generic Authorization credentials to the request" >> {
4246
val creds = GenericHttpCredentials("OAuth", Map("oauth_version" -> "1.0"))
4347
Get() ~> addCredentials(creds) === HttpRequest(headers = List(Authorization(creds)))

0 commit comments

Comments
 (0)