Skip to content

Commit fe27f93

Browse files
feat(client): add HttpRequest#url() method
1 parent 7d05a12 commit fe27f93

File tree

3 files changed

+141
-1
lines changed

3 files changed

+141
-1
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright 2025 Lithic
189+
Copyright 2026 Lithic
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

lithic-java-core/src/main/kotlin/com/lithic/api/core/http/HttpRequest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.lithic.api.core.http
22

33
import com.lithic.api.core.checkRequired
44
import com.lithic.api.core.toImmutable
5+
import java.net.URLEncoder
56

67
class HttpRequest
78
private constructor(
@@ -13,6 +14,35 @@ private constructor(
1314
@get:JvmName("body") val body: HttpRequestBody?,
1415
) {
1516

17+
fun url(): String = buildString {
18+
append(baseUrl)
19+
20+
pathSegments.forEach { segment ->
21+
if (!endsWith("/")) {
22+
append("/")
23+
}
24+
append(URLEncoder.encode(segment, "UTF-8"))
25+
}
26+
27+
if (queryParams.isEmpty()) {
28+
return@buildString
29+
}
30+
31+
append("?")
32+
var isFirst = true
33+
queryParams.keys().forEach { key ->
34+
queryParams.values(key).forEach { value ->
35+
if (!isFirst) {
36+
append("&")
37+
}
38+
append(URLEncoder.encode(key, "UTF-8"))
39+
append("=")
40+
append(URLEncoder.encode(value, "UTF-8"))
41+
isFirst = false
42+
}
43+
}
44+
}
45+
1646
fun toBuilder(): Builder = Builder().from(this)
1747

1848
override fun toString(): String =
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.lithic.api.core.http
2+
3+
import org.assertj.core.api.Assertions.assertThat
4+
import org.junit.jupiter.params.ParameterizedTest
5+
import org.junit.jupiter.params.provider.EnumSource
6+
7+
internal class HttpRequestTest {
8+
9+
enum class UrlTestCase(val request: HttpRequest, val expectedUrl: String) {
10+
BASE_URL_ONLY(
11+
HttpRequest.builder().method(HttpMethod.GET).baseUrl("https://api.example.com").build(),
12+
expectedUrl = "https://api.example.com",
13+
),
14+
BASE_URL_WITH_TRAILING_SLASH(
15+
HttpRequest.builder()
16+
.method(HttpMethod.GET)
17+
.baseUrl("https://api.example.com/")
18+
.build(),
19+
expectedUrl = "https://api.example.com/",
20+
),
21+
SINGLE_PATH_SEGMENT(
22+
HttpRequest.builder()
23+
.method(HttpMethod.GET)
24+
.baseUrl("https://api.example.com")
25+
.addPathSegment("users")
26+
.build(),
27+
expectedUrl = "https://api.example.com/users",
28+
),
29+
MULTIPLE_PATH_SEGMENTS(
30+
HttpRequest.builder()
31+
.method(HttpMethod.GET)
32+
.baseUrl("https://api.example.com")
33+
.addPathSegments("users", "123", "profile")
34+
.build(),
35+
expectedUrl = "https://api.example.com/users/123/profile",
36+
),
37+
PATH_SEGMENT_WITH_SPECIAL_CHARS(
38+
HttpRequest.builder()
39+
.method(HttpMethod.GET)
40+
.baseUrl("https://api.example.com")
41+
.addPathSegment("user name")
42+
.build(),
43+
expectedUrl = "https://api.example.com/user+name",
44+
),
45+
SINGLE_QUERY_PARAM(
46+
HttpRequest.builder()
47+
.method(HttpMethod.GET)
48+
.baseUrl("https://api.example.com")
49+
.addPathSegment("users")
50+
.putQueryParam("limit", "10")
51+
.build(),
52+
expectedUrl = "https://api.example.com/users?limit=10",
53+
),
54+
MULTIPLE_QUERY_PARAMS(
55+
HttpRequest.builder()
56+
.method(HttpMethod.GET)
57+
.baseUrl("https://api.example.com")
58+
.addPathSegment("users")
59+
.putQueryParam("limit", "10")
60+
.putQueryParam("offset", "20")
61+
.build(),
62+
expectedUrl = "https://api.example.com/users?limit=10&offset=20",
63+
),
64+
QUERY_PARAM_WITH_SPECIAL_CHARS(
65+
HttpRequest.builder()
66+
.method(HttpMethod.GET)
67+
.baseUrl("https://api.example.com")
68+
.addPathSegment("search")
69+
.putQueryParam("q", "hello world")
70+
.build(),
71+
expectedUrl = "https://api.example.com/search?q=hello+world",
72+
),
73+
MULTIPLE_VALUES_SAME_PARAM(
74+
HttpRequest.builder()
75+
.method(HttpMethod.GET)
76+
.baseUrl("https://api.example.com")
77+
.addPathSegment("users")
78+
.putQueryParams("tags", listOf("admin", "user"))
79+
.build(),
80+
expectedUrl = "https://api.example.com/users?tags=admin&tags=user",
81+
),
82+
BASE_URL_WITH_TRAILING_SLASH_AND_PATH(
83+
HttpRequest.builder()
84+
.method(HttpMethod.GET)
85+
.baseUrl("https://api.example.com/")
86+
.addPathSegment("users")
87+
.build(),
88+
expectedUrl = "https://api.example.com/users",
89+
),
90+
COMPLEX_URL(
91+
HttpRequest.builder()
92+
.method(HttpMethod.POST)
93+
.baseUrl("https://api.example.com")
94+
.addPathSegments("v1", "users", "123")
95+
.putQueryParams("include", listOf("profile", "settings"))
96+
.putQueryParam("format", "json")
97+
.build(),
98+
expectedUrl =
99+
"https://api.example.com/v1/users/123?include=profile&include=settings&format=json",
100+
),
101+
}
102+
103+
@ParameterizedTest
104+
@EnumSource
105+
fun url(testCase: UrlTestCase) {
106+
val actualUrl = testCase.request.url()
107+
108+
assertThat(actualUrl).isEqualTo(testCase.expectedUrl)
109+
}
110+
}

0 commit comments

Comments
 (0)