Skip to content

Commit e4d6b15

Browse files
Jens ZettelmeyerJens Zettelmeyer
authored andcommitted
Add client implementation that uses the jdk build in http client
1 parent 6b73349 commit e4d6b15

File tree

35 files changed

+2659
-18
lines changed

35 files changed

+2659
-18
lines changed

end2end-tests/build.gradle.kts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
val fabrikt: Configuration by configurations.creating
22

33
val generationDir = "$buildDir/generated"
4-
val apiFile = "$buildDir/../../src/test/resources/examples/okHttpClient/api.yaml"
4+
val apiFile = "$buildDir/../../src/test/resources/examples/httpClient/api.yaml"
55

66
sourceSets {
77
main { java.srcDirs("$generationDir/src/main/kotlin") }
@@ -40,7 +40,7 @@ dependencies {
4040

4141
tasks {
4242

43-
val generateCode by creating(JavaExec::class) {
43+
val generateOkioCode by creating(JavaExec::class) {
4444
inputs.files(apiFile)
4545
outputs.dir(generationDir)
4646
outputs.cacheIf { true }
@@ -58,9 +58,29 @@ tasks {
5858
dependsOn(":shadowJar")
5959
}
6060

61+
val generateJdkClientCode by creating(JavaExec::class) {
62+
inputs.files(apiFile)
63+
outputs.dir(generationDir)
64+
outputs.cacheIf { true }
65+
classpath = rootProject.files("./build/libs/fabrikt-${rootProject.version}.jar")
66+
mainClass.set("com.cjbooms.fabrikt.cli.CodeGen")
67+
args = listOf(
68+
"--output-directory", generationDir,
69+
"--base-package", "com.example.jdk_client",
70+
"--api-file", apiFile,
71+
"--targets", "http_models",
72+
"--http-client-target", "JDK_HTTP",
73+
"--targets", "client",
74+
"--http-client-opts", "resilience4j"
75+
)
76+
dependsOn(":jar")
77+
dependsOn(":shadowJar")
78+
}
79+
6180
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
6281
kotlinOptions.jvmTarget = "17"
63-
dependsOn(generateCode)
82+
dependsOn(generateOkioCode)
83+
dependsOn(generateJdkClientCode)
6484
}
6585

6686

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package com.cjbooms.fabrikt.clients.jdk
2+
3+
import com.example.jdk_client.client.ApiClientException
4+
import com.example.jdk_client.client.ApiRedirectException
5+
import com.example.jdk_client.client.ApiServerException
6+
import com.example.jdk_client.client.ExamplePath2Client
7+
import com.example.jdk_client.client.ExamplePath3SubresourceClient
8+
import com.example.jdk_client.client.ExamplePath1Client
9+
import com.example.jdk_client.models.Failure
10+
import com.example.jdk_client.models.FirstModel
11+
import com.example.jdk_client.models.QueryResult
12+
import com.fasterxml.jackson.databind.ObjectMapper
13+
import com.github.tomakehurst.wiremock.WireMockServer
14+
import com.github.tomakehurst.wiremock.common.ConsoleNotifier
15+
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
16+
import com.marcinziolo.kotlin.wiremock.*
17+
import org.assertj.core.api.Assertions
18+
import org.junit.jupiter.api.*
19+
import org.junit.jupiter.params.ParameterizedTest
20+
import org.junit.jupiter.params.provider.MethodSource
21+
import java.net.ServerSocket
22+
import java.net.http.HttpClient
23+
import java.util.*
24+
import java.util.stream.Stream
25+
26+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
27+
class JdkTest {
28+
private val port: Int = ServerSocket(0).use { socket -> socket.localPort }
29+
30+
private val wiremock: WireMockServer = WireMockServer(
31+
WireMockConfiguration.options().port(port).notifier(
32+
ConsoleNotifier(true)
33+
))
34+
35+
private val mapper = ObjectMapper()
36+
private val httpClient = HttpClient.newHttpClient()
37+
private val examplePath1Client = ExamplePath1Client(mapper, "http://localhost:$port", httpClient)
38+
private val examplePath2Client = ExamplePath2Client(mapper, "http://localhost:$port", httpClient)
39+
private val examplePath3Client = ExamplePath3SubresourceClient(mapper, "http://localhost:$port", httpClient)
40+
41+
private val uuid = UUID.randomUUID()
42+
private val failure = Failure(traceId = uuid,
43+
error = "testError",
44+
errorCode = "testErrorCode")
45+
46+
@Suppress("unused")
47+
private fun path2ErrorCodes(): Stream<Int> = Stream.of(400, 422, 423)
48+
49+
@BeforeEach
50+
fun setUp() {
51+
wiremock.start()
52+
}
53+
54+
@AfterEach
55+
fun afterEach() {
56+
wiremock.resetAll()
57+
wiremock.stop()
58+
}
59+
60+
@Test
61+
fun `throws an exception if 404 is returned`() {
62+
wiremock.get {
63+
url like "/example-path-1"
64+
} returns {
65+
statusCode = 404
66+
}
67+
68+
val result = assertThrows<ApiClientException> {
69+
examplePath1Client.getExamplePath1()
70+
}
71+
Assertions.assertThat(result.statusCode).isEqualTo(404)
72+
}
73+
74+
@Test
75+
fun `returns data when no query parameters are send`(testInfo: TestInfo) {
76+
wiremock.get {
77+
url like "/example-path-1"
78+
} returns {
79+
statusCode = 200
80+
body = mapper.writeValueAsString(
81+
QueryResult(
82+
listOf(FirstModel(id = testInfo.displayName))
83+
)
84+
)
85+
}
86+
87+
val result = examplePath1Client.getExamplePath1()
88+
89+
Assertions.assertThat(result.data).isEqualTo(
90+
QueryResult(
91+
listOf(FirstModel(id = testInfo.displayName))
92+
)
93+
)
94+
}
95+
96+
@Test
97+
fun `adds query_param2 to the query`(testInfo: TestInfo) {
98+
wiremock.get {
99+
urlPath like "/example-path-1"
100+
queryParams contains "query_param2" like "10"
101+
} returns {
102+
statusCode = 200
103+
body = mapper.writeValueAsString(
104+
QueryResult(
105+
listOf(FirstModel(id = testInfo.displayName))
106+
)
107+
)
108+
}
109+
110+
val result = examplePath1Client.getExamplePath1(queryParam2 = 10)
111+
112+
Assertions.assertThat(result.data).isEqualTo(
113+
QueryResult(
114+
listOf(FirstModel(id = testInfo.displayName))
115+
)
116+
)
117+
}
118+
119+
@Test
120+
fun `adds explode_list_query_param to the query`(testInfo: TestInfo) {
121+
wiremock.get {
122+
urlPath like "/example-path-1"
123+
queryParams contains "explode_list_query_param" like "list"
124+
queryParams contains "explode_list_query_param" like "of"
125+
queryParams contains "explode_list_query_param" like "parameters"
126+
} returns {
127+
statusCode = 200
128+
body = mapper.writeValueAsString(
129+
QueryResult(
130+
listOf(FirstModel(id = testInfo.displayName))
131+
)
132+
)
133+
}
134+
135+
val result = examplePath1Client.getExamplePath1(explodeListQueryParam = listOf("list", "of", "parameters"))
136+
137+
Assertions.assertThat(result.data).isEqualTo(
138+
QueryResult(
139+
listOf(FirstModel(id = testInfo.displayName))
140+
)
141+
)
142+
}
143+
144+
@Test
145+
fun `adds additional headers to the query`(testInfo: TestInfo) {
146+
wiremock.get {
147+
urlPath like "/example-path-1"
148+
headers contains "awesome" like "header"
149+
} returns {
150+
statusCode = 200
151+
body = mapper.writeValueAsString(
152+
QueryResult(
153+
listOf(FirstModel(id = testInfo.displayName))
154+
)
155+
)
156+
}
157+
158+
val result = examplePath1Client.getExamplePath1(additionalHeaders = mapOf("awesome" to "header"))
159+
160+
Assertions.assertThat(result.data).isEqualTo(
161+
QueryResult(
162+
listOf(FirstModel(id = testInfo.displayName))
163+
)
164+
)
165+
}
166+
167+
@Test
168+
fun `send body with post request`(testInfo: TestInfo) {
169+
val content = FirstModel(id = testInfo.displayName)
170+
wiremock.post {
171+
urlPath like "/example-path-1"
172+
body equalTo mapper.writeValueAsString(content)
173+
} returns {
174+
statusCode = 201
175+
}
176+
177+
val result = examplePath1Client.postExamplePath1(content)
178+
Assertions.assertThat(result.statusCode).isEqualTo(201)
179+
}
180+
181+
@ParameterizedTest
182+
@MethodSource("path2ErrorCodes")
183+
fun `throws an exception if a 4xx http status code is returned`(errorCode: Int) {
184+
wiremock.get {
185+
urlPath like "/example-path-2/$errorCode"
186+
} returns {
187+
statusCode = errorCode
188+
body = mapper.writeValueAsString(failure)
189+
}
190+
191+
val result = assertThrows<ApiClientException> {
192+
examplePath2Client.getExamplePath2PathParam(errorCode.toString(), 10)
193+
}
194+
195+
Assertions.assertThat(result.statusCode).isEqualTo(errorCode)
196+
Assertions.assertThat(mapper.readValue(result.message, Failure::class.java)).isEqualTo(failure)
197+
}
198+
199+
@Test
200+
fun `throws an exception if a http status code 500 is returned`() {
201+
wiremock.get {
202+
urlPath like "/example-path-2/500"
203+
} returns {
204+
statusCode = 500
205+
body = mapper.writeValueAsString(failure)
206+
}
207+
208+
val result = assertThrows<ApiServerException> {
209+
examplePath2Client.getExamplePath2PathParam("500", 10)
210+
}
211+
212+
Assertions.assertThat(result.statusCode).isEqualTo(500)
213+
Assertions.assertThat(mapper.readValue(result.message, Failure::class.java)).isEqualTo(failure)
214+
}
215+
216+
@Test
217+
fun `throws an exception if a http status code 304 is returned`() {
218+
wiremock.get {
219+
urlPath like "/example-path-2/304"
220+
} returns {
221+
statusCode = 304
222+
}
223+
224+
val result = assertThrows<ApiRedirectException> {
225+
examplePath2Client.getExamplePath2PathParam("304", 10)
226+
}
227+
228+
Assertions.assertThat(result.statusCode).isEqualTo(304)
229+
}
230+
231+
232+
@Test
233+
fun `head returns 200`() {
234+
wiremock.head {
235+
urlPath like "/example-path-2/head200"
236+
} returns {
237+
statusCode = 200
238+
}
239+
240+
val result = examplePath2Client.headOperationIdExample("head200")
241+
242+
Assertions.assertThat(result.statusCode).isEqualTo(200)
243+
}
244+
245+
@Test
246+
fun `put returns 204`() {
247+
val model = FirstModel(id = "put", secondAttr = "204")
248+
wiremock.put {
249+
urlPath like "/example-path-2/put204"
250+
body equalTo mapper.writeValueAsString(model)
251+
headers contains "If-Match" like "match"
252+
} returns {
253+
statusCode = 204
254+
}
255+
256+
val result = examplePath2Client.putExamplePath2PathParam(firstModel = model, pathParam = "put204", ifMatch = "match")
257+
258+
Assertions.assertThat(result.statusCode).isEqualTo(204)
259+
}
260+
261+
@Test
262+
fun `put returns 204 with sub resource`() {
263+
val model = FirstModel(id = "put", secondAttr = "304")
264+
wiremock.put {
265+
urlPath like "/example-path-3/put304/subresource"
266+
body equalTo mapper.writeValueAsString(model)
267+
headers contains "If-Match" like "match"
268+
} returns {
269+
statusCode = 204
270+
}
271+
272+
val result = examplePath3Client.putExamplePath3PathParamSubresource(firstModel = model, pathParam = "put304", ifMatch = "match")
273+
274+
Assertions.assertThat(result.statusCode).isEqualTo(204)
275+
}
276+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.cjbooms.fabrikt.clients.jdk
2+
3+
import com.example.jdk_client.client.Url
4+
import org.assertj.core.api.Assertions.assertThat
5+
import org.junit.jupiter.api.Test
6+
import java.net.URI
7+
8+
class UrlTest {
9+
10+
@Test
11+
fun testExpansion() {
12+
val url = Url("http://bla.blub/{a}/test/{b}")
13+
.addPathParam("a", "123")
14+
.addPathParam("b", "abc")
15+
.addQueryParam("expand", "true")
16+
.addQueryParam("whatever", "654")
17+
18+
assertThat(url.toUri())
19+
.isEqualTo(URI.create("http://bla.blub/123/test/abc?expand=true&whatever=654"))
20+
}
21+
22+
@Test
23+
fun testWithoutQueryParams() {
24+
val url = Url("http://bla.blub/{a}/test/{b}")
25+
.addPathParam("a", "123")
26+
.addPathParam("b", "abc")
27+
28+
assertThat(url.toUri())
29+
.isEqualTo(URI.create("http://bla.blub/123/test/abc"))
30+
}
31+
32+
@Test
33+
fun testArrayWithoutExplode() {
34+
val url = Url("http://bla.blub/test")
35+
.addQueryParam("queryParam", listOf("a", "b"), true)
36+
37+
assertThat(url.toUri())
38+
.isEqualTo(URI.create("http://bla.blub/test?queryParam=a&queryParam=b"))
39+
}
40+
41+
@Test
42+
fun testArrayWithExplode() {
43+
val url = Url("http://bla.blub/test")
44+
.addQueryParam("queryParam", listOf("a", "b"), false)
45+
46+
assertThat(url.toUri())
47+
.isEqualTo(URI.create("http://bla.blub/test?queryParam=a,b"))
48+
}
49+
}

src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ enum class ClientCodeGenOptionType(private val description: String) {
3131

3232
enum class ClientCodeGenTargetType(val description: String) {
3333
OK_HTTP("Generate OkHttp client."),
34-
OPEN_FEIGN("Generate OpenFeign client.");
34+
OPEN_FEIGN("Generate OpenFeign client."),
35+
JDK_HTTP("Generate JDK HTTP client.");
3536

3637
override fun toString() = "`${super.toString()}` - $description"
3738
}

0 commit comments

Comments
 (0)