From a1151f1d5d1c287ab7975c2179bb9b7722e1211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Fri, 10 Apr 2026 10:53:23 +0200 Subject: [PATCH 1/2] fix: correct baseUrl interpolation and remove spurious body null checks baseUrl was rendered as literal text (${'$'}{baseUrl}) instead of being interpolated, so generated HTTP clients never substituted the actual URL. Endpoints without a request body incorrectly emitted `if (body != null)` which resolved to HttpRequestBuilder.body and added a JSON Content-Type header to every bodyless request. Co-Authored-By: Claude Opus 4.6 (1M context) --- build.gradle.kts | 2 +- .../core/gen/client/BodyGenerator.kt | 10 +++--- .../justworks/core/gen/ClientGeneratorTest.kt | 36 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2e9bcb6..d428279 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ allprojects { version = System .getenv("RELEASE_VERSION") ?.let { Regex("""^v(\d+\.\d+\.\d+.*)$""").matchEntire(it)?.groupValues?.get(1) } - ?: "0.0.1-SNAPSHOT" + ?: "0.0.3-SNAPSHOT" repositories { mavenCentral() diff --git a/core/src/main/kotlin/com/avsystem/justworks/core/gen/client/BodyGenerator.kt b/core/src/main/kotlin/com/avsystem/justworks/core/gen/client/BodyGenerator.kt index 133ab02..85bbe6f 100644 --- a/core/src/main/kotlin/com/avsystem/justworks/core/gen/client/BodyGenerator.kt +++ b/core/src/main/kotlin/com/avsystem/justworks/core/gen/client/BodyGenerator.kt @@ -82,9 +82,11 @@ internal object BodyGenerator { beginControlFlow("$CLIENT.%M(%L)", httpMethodFun, urlString) addCommonRequestParts(params) - optionalGuard(endpoint.requestBody?.required ?: false, BODY) { - addStatement("%M(%T.Json)", CONTENT_TYPE_FUN, CONTENT_TYPE_APPLICATION) - addStatement("%M(%L)", SET_BODY_FUN, BODY) + if (endpoint.requestBody != null) { + optionalGuard(endpoint.requestBody.required, BODY) { + addStatement("%M(%T.Json)", CONTENT_TYPE_FUN, CONTENT_TYPE_APPLICATION) + addStatement("%M(%L)", SET_BODY_FUN, BODY) + } } // Don't endControlFlow here — the outer buildFunctionBody closes with .toResult() @@ -198,7 +200,7 @@ internal object BodyGenerator { private fun buildUrlString(endpoint: Endpoint, params: Map>): CodeBlock { val (format, args) = params[ParameterLocation.PATH] .orEmpty() - .fold($$"${'$'}{$$BASE_URL}" + endpoint.path to emptyList()) { (format, args), param -> + .fold($$"${%L}" + endpoint.path to listOf(BASE_URL)) { (format, args), param -> format.replace("{${param.name}}", $$"${%M(%L)}") to args + ENCODE_PARAM_FUN + param.name.toCamelCase() } return CodeBlock.of("%P", CodeBlock.of(format, *args.toTypedArray())) diff --git a/core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt b/core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt index 5f295ec..efb0094 100644 --- a/core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt +++ b/core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt @@ -536,6 +536,42 @@ class ClientGeneratorTest { assertFalse(body.contains("submitForm"), "Should NOT contain submitForm for JSON") } + // -- No body: endpoint without requestBody should not emit setBody or contentType -- + + @Test + fun `endpoint without requestBody does not generate body null check`() { + val ep = endpoint( + method = HttpMethod.GET, + operationId = "listPets", + requestBody = null, + ) + val cls = clientClass(ep) + val funSpec = cls.funSpecs.first { it.name == "listPets" } + val body = funSpec.body.toString() + assertFalse(body.contains("setBody"), "Should NOT contain setBody when no requestBody") + assertFalse(body.contains("contentType"), "Should NOT set contentType when no requestBody") + assertFalse(body.contains("if (body"), "Should NOT check body != null when no requestBody") + } + + // -- URL interpolation: baseUrl must be interpolated, not literal -- + + @Test + fun `generated URL interpolates baseUrl property`() { + val ep = endpoint( + path = "/pets/{petId}", + operationId = "getPet", + parameters = listOf( + Parameter("petId", ParameterLocation.PATH, true, TypeRef.Primitive(PrimitiveType.LONG), null), + ), + ) + val cls = clientClass(ep) + val funSpec = cls.funSpecs.first { it.name == "getPet" } + val body = funSpec.body.toString() + // Must contain ${baseUrl} as interpolation, not ${'$'}{baseUrl} (escaped/literal) + assertTrue(body.contains("\${baseUrl}"), "Expected \${baseUrl} interpolation in URL") + assertFalse(body.contains("\${'$'}{baseUrl}"), "baseUrl must not be escaped as literal text") + } + // -- CONT-02: Form-urlencoded code generation -- @Test From 7b80eda28fedb90b258e2416aa639574d7a4735f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Fri, 10 Apr 2026 11:28:22 +0200 Subject: [PATCH 2/2] fix: set default version to 0.0.1-SNAPSHOT if RELEASE_VERSION is undefined --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index d428279..2e9bcb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ allprojects { version = System .getenv("RELEASE_VERSION") ?.let { Regex("""^v(\d+\.\d+\.\d+.*)$""").matchEntire(it)?.groupValues?.get(1) } - ?: "0.0.3-SNAPSHOT" + ?: "0.0.1-SNAPSHOT" repositories { mavenCentral()