Skip to content

Commit cf88471

Browse files
Added ApiHttpData
1 parent 7b48b44 commit cf88471

File tree

7 files changed

+281
-89
lines changed

7 files changed

+281
-89
lines changed

core/src/main/scala/scommons/api/http/ApiHttpClient.scala

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package scommons.api.http
22

3+
import play.api.libs.json.Json.{stringify, toJson}
34
import play.api.libs.json._
45
import scommons.api.http.ApiHttpClient._
6+
import scommons.api.http.ApiHttpData.StringData
57
import scommons.api.http.ApiHttpMethod._
68

79
import scala.concurrent.duration._
@@ -17,46 +19,46 @@ abstract class ApiHttpClient(baseUrl: String,
1719
timeout: FiniteDuration = defaultTimeout
1820
)(implicit jsonReads: Reads[R]): Future[R] = {
1921

20-
exec[String](GET, url, params, headers, None, timeout).map(parseResponse[R])
22+
exec(GET, url, params, headers, None, timeout).map(parseResponse[R])
2123
}
2224

2325
def execPost[D, R](url: String,
2426
data: D,
2527
params: List[(String, String)] = Nil,
2628
headers: List[(String, String)] = Nil,
2729
timeout: FiniteDuration = defaultTimeout
28-
)(implicit jsonWrites: Writes[D], jsonReads: Reads[R]): Future[R] = {
30+
)(implicit writes: Writes[D], reads: Reads[R]): Future[R] = {
2931

30-
exec(POST, url, params, headers, Some(data), timeout).map(parseResponse[R])
32+
exec(POST, url, params, headers, Some(StringData(stringify(toJson(data)))), timeout).map(parseResponse[R])
3133
}
3234

3335
def execPut[D, R](url: String,
3436
data: D,
3537
params: List[(String, String)] = Nil,
3638
headers: List[(String, String)] = Nil,
3739
timeout: FiniteDuration = defaultTimeout
38-
)(implicit jsonWrites: Writes[D], jsonReads: Reads[R]): Future[R] = {
40+
)(implicit writes: Writes[D], reads: Reads[R]): Future[R] = {
3941

40-
exec(PUT, url, params, headers, Some(data), timeout).map(parseResponse[R])
42+
exec(PUT, url, params, headers, Some(StringData(stringify(toJson(data)))), timeout).map(parseResponse[R])
4143
}
4244

4345
def execDelete[D, R](url: String,
4446
data: Option[D] = None,
4547
params: List[(String, String)] = Nil,
4648
headers: List[(String, String)] = Nil,
4749
timeout: FiniteDuration = defaultTimeout
48-
)(implicit jsonWrites: Writes[D], jsonReads: Reads[R]): Future[R] = {
50+
)(implicit writes: Writes[D], reads: Reads[R]): Future[R] = {
4951

50-
exec(DELETE, url, params, headers, data, timeout).map(parseResponse[R])
52+
exec(DELETE, url, params, headers, data.map(d => StringData(stringify(toJson(d)))), timeout).map(parseResponse[R])
5153
}
5254

53-
private def exec[T](method: ApiHttpMethod,
54-
url: String,
55-
params: List[(String, String)],
56-
headers: List[(String, String)],
57-
data: Option[T],
58-
timeout: FiniteDuration
59-
)(implicit jsonWrites: Writes[T]): Future[ApiHttpResponse] = {
55+
def exec(method: ApiHttpMethod,
56+
url: String,
57+
params: List[(String, String)],
58+
headers: List[(String, String)],
59+
data: Option[ApiHttpData],
60+
timeout: FiniteDuration
61+
): Future[ApiHttpResponse] = {
6062

6163
val targetUrl = getTargetUrl(baseUrl, url)
6264

@@ -65,9 +67,7 @@ abstract class ApiHttpClient(baseUrl: String,
6567
targetUrl = targetUrl,
6668
params = params,
6769
headers = headers,
68-
jsonBody = data.map { d =>
69-
Json.stringify(Json.toJson(d))
70-
},
70+
data = data,
7171
timeout = timeout
7272
).map {
7373
case None => throw ApiHttpTimeoutException(targetUrl)
@@ -79,7 +79,7 @@ abstract class ApiHttpClient(baseUrl: String,
7979
targetUrl: String,
8080
params: List[(String, String)],
8181
headers: List[(String, String)],
82-
jsonBody: Option[String],
82+
data: Option[ApiHttpData],
8383
timeout: FiniteDuration
8484
): Future[Option[ApiHttpResponse]]
8585
}
@@ -118,13 +118,16 @@ object ApiHttpClient {
118118
}.toList
119119

120120
private[http] def getTargetUrl(baseUrl: String, url: String): String = {
121-
val normalizedUrl =
122-
if (url.startsWith("/")) url.substring(1)
123-
else url
124-
125-
if (baseUrl.endsWith("/"))
126-
s"$baseUrl$normalizedUrl"
127-
else
128-
s"$baseUrl/$normalizedUrl"
121+
if (baseUrl.isEmpty) url
122+
else {
123+
val normalizedUrl =
124+
if (url.startsWith("/")) url.substring(1)
125+
else url
126+
127+
if (baseUrl.endsWith("/"))
128+
s"$baseUrl$normalizedUrl"
129+
else
130+
s"$baseUrl/$normalizedUrl"
131+
}
129132
}
130133
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scommons.api.http
2+
3+
sealed trait ApiHttpData {
4+
5+
def contentType: String
6+
}
7+
8+
object ApiHttpData {
9+
10+
case class StringData(data: String,
11+
contentType: String = "application/json"
12+
) extends ApiHttpData
13+
14+
case class UrlEncodedFormData(data: Map[String, Seq[String]]) extends ApiHttpData {
15+
val contentType = "application/x-www-form-urlencoded"
16+
}
17+
}

core/src/test/scala/scommons/api/http/ApiHttpClientSpec.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import play.api.libs.json.Json.{stringify, toJson}
66
import play.api.libs.json._
77
import scommons.api.http.ApiHttpClient._
88
import scommons.api.http.ApiHttpClientSpec._
9+
import scommons.api.http.ApiHttpData.{StringData, UrlEncodedFormData}
10+
import scommons.api.http.ApiHttpMethod.GET
911

1012
import scala.concurrent.Future
1113
import scala.concurrent.duration._
@@ -22,11 +24,11 @@ class ApiHttpClientSpec extends AsyncFlatSpec
2224
private val defaultTimeout = 25.seconds
2325

2426
private type HttpExecute =
25-
(String, String, List[(String, String)], List[(String, String)], Option[String], FiniteDuration) =>
27+
(String, String, List[(String, String)], List[(String, String)], Option[ApiHttpData], FiniteDuration) =>
2628
Future[Option[ApiHttpResponse]]
2729

2830
private def stubExec = stubFunction[
29-
String, String, List[(String, String)], List[(String, String)], Option[String], FiniteDuration,
31+
String, String, List[(String, String)], List[(String, String)], Option[ApiHttpData], FiniteDuration,
3032
Future[Option[ApiHttpResponse]]
3133
]
3234

@@ -35,11 +37,11 @@ class ApiHttpClientSpec extends AsyncFlatSpec
3537
targetUrl: String,
3638
params: List[(String, String)],
3739
headers: List[(String, String)],
38-
jsonBody: Option[String],
40+
data: Option[ApiHttpData],
3941
timeout: FiniteDuration
4042
): Future[Option[ApiHttpResponse]] = {
4143

42-
exec(method, targetUrl, params, headers, jsonBody, timeout)
44+
exec(method, targetUrl, params, headers, data, timeout)
4345
}
4446
}
4547

@@ -48,13 +50,14 @@ class ApiHttpClientSpec extends AsyncFlatSpec
4850
val url = "/api/get/url"
4951
val execute = stubExec
5052
val client = new TestHttpClient(execute)
53+
val data = UrlEncodedFormData(Map("test" -> Seq("test value")))
5154

5255
execute.when(*, *, *, *, *, *).returns(Future.successful(None))
5356

5457
//when
55-
client.execGet[List[TestRespData]](url, params, headers).failed.map { ex =>
58+
client.exec(GET, url, params, headers, Some(data), defaultTimeout).failed.map { ex =>
5659
//then
57-
execute.verify("GET", s"$baseUrl$url", params, headers, None, defaultTimeout)
60+
execute.verify("GET", s"$baseUrl$url", params, headers, Some(data), defaultTimeout)
5861

5962
ex shouldBe ApiHttpTimeoutException(s"$baseUrl$url")
6063

@@ -116,7 +119,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
116119
//when
117120
client.execPost[TestReqData, List[TestRespData]](url, data, params, headers, timeout).map { result =>
118121
//then
119-
execute.verify("POST", s"$baseUrl$url", params, headers, Some(stringify(toJson(data))), timeout)
122+
execute.verify("POST", s"$baseUrl$url", params, headers, Some(StringData(stringify(toJson(data)))), timeout)
120123

121124
result shouldBe expectedResult
122125
}
@@ -136,7 +139,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
136139
//when
137140
client.execPut[TestReqData, List[TestRespData]](url, data, params, headers, timeout).map { result =>
138141
//then
139-
execute.verify("PUT", s"$baseUrl$url", params, headers, Some(stringify(toJson(data))), timeout)
142+
execute.verify("PUT", s"$baseUrl$url", params, headers, Some(StringData(stringify(toJson(data)))), timeout)
140143

141144
result shouldBe expectedResult
142145
}
@@ -156,7 +159,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
156159
//when
157160
client.execDelete[TestReqData, List[TestRespData]](url, Some(data), params, headers, timeout).map { result =>
158161
//then
159-
execute.verify("DELETE", s"$baseUrl$url", params, headers, Some(stringify(toJson(data))), timeout)
162+
execute.verify("DELETE", s"$baseUrl$url", params, headers, Some(StringData(stringify(toJson(data)))), timeout)
160163

161164
result shouldBe expectedResult
162165
}
@@ -253,6 +256,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
253256

254257
it should "return normalized target url when getTargetUrl" in {
255258
//when & then
259+
getTargetUrl("", "/some/url") shouldBe "/some/url"
256260
getTargetUrl("http://test.com", "some/url") shouldBe "http://test.com/some/url"
257261
getTargetUrl("http://test.com/", "some/url") shouldBe "http://test.com/some/url"
258262
getTargetUrl("http://test.com", "/some/url") shouldBe "http://test.com/some/url"

dom/src/main/scala/scommons/api/http/dom/DomApiHttpClient.scala

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package scommons.api.http.dom
22

33
import org.scalajs.dom
4+
import scommons.api.http.ApiHttpData._
45
import scommons.api.http.dom.DomApiHttpClient._
5-
import scommons.api.http.{ApiHttpClient, ApiHttpResponse}
6+
import scommons.api.http.{ApiHttpClient, ApiHttpData, ApiHttpResponse}
67

78
import scala.concurrent.duration._
89
import scala.concurrent.{Future, Promise}
@@ -16,23 +17,27 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco
1617
targetUrl: String,
1718
params: List[(String, String)],
1819
headers: List[(String, String)],
19-
jsonBody: Option[String],
20+
data: Option[ApiHttpData],
2021
timeout: FiniteDuration): Future[Option[ApiHttpResponse]] = {
2122

2223
val req = createRequest()
2324
req.open(method, getFullUrl(targetUrl, params))
2425
req.timeout = timeout.toMillis.toInt
2526

26-
val allHeaders = {
27-
if (jsonBody.isDefined) {
28-
headers ++ Map("Content-Type" -> "application/json")
29-
}
30-
else headers
27+
val allHeaders = data match {
28+
case None => headers
29+
case Some(d) => headers ++ Map("Content-Type" -> d.contentType)
3130
}
3231

3332
allHeaders.foreach(x => req.setRequestHeader(x._1, x._2))
3433

35-
execute(req, jsonBody).map {
34+
val body = data.map {
35+
case StringData(d, _) => d
36+
case UrlEncodedFormData(d) =>
37+
d.flatMap(item => item._2.map(c => s"${item._1}=${js.URIUtils.encodeURIComponent(c)}")).mkString("&")
38+
}
39+
40+
execute(req, body).map {
3641
case res if res.status == 0 => None //timeout
3742
case res => Some(ApiHttpResponse(
3843
url = targetUrl,
@@ -45,7 +50,7 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco
4550

4651
private[dom] def createRequest(): dom.XMLHttpRequest = new dom.XMLHttpRequest()
4752

48-
private[dom] def execute(req: dom.XMLHttpRequest, body: Option[String]): Future[dom.XMLHttpRequest] = {
53+
private def execute(req: dom.XMLHttpRequest, body: Option[String]): Future[dom.XMLHttpRequest] = {
4954
val promise = Promise[dom.XMLHttpRequest]()
5055

5156
req.onreadystatechange = { (_: dom.Event) =>

0 commit comments

Comments
 (0)