From 289c4da88413def81f42c5c4c97196e6faece086 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 27 Jun 2026 22:57:35 +0200 Subject: [PATCH] Close responses on error throws --- .../com/openai/core/handlers/ErrorHandler.kt | 84 ++++++++++-------- .../openai/core/handlers/ErrorHandlerTest.kt | 85 +++++++++++++++++++ 2 files changed, 135 insertions(+), 34 deletions(-) create mode 100644 openai-java-core/src/test/kotlin/com/openai/core/handlers/ErrorHandlerTest.kt diff --git a/openai-java-core/src/main/kotlin/com/openai/core/handlers/ErrorHandler.kt b/openai-java-core/src/main/kotlin/com/openai/core/handlers/ErrorHandler.kt index 6d6ee571c..5809e1c99 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/handlers/ErrorHandler.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/handlers/ErrorHandler.kt @@ -48,46 +48,62 @@ internal fun errorHandler( when (val statusCode = response.statusCode()) { in 200..299 -> response 400 -> - throw BadRequestException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw BadRequestException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } 401 -> - throw UnauthorizedException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw UnauthorizedException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } 403 -> - throw PermissionDeniedException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw PermissionDeniedException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } 404 -> - throw NotFoundException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw NotFoundException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } 422 -> - throw UnprocessableEntityException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw UnprocessableEntityException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } 429 -> - throw RateLimitException.builder() - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw RateLimitException.builder() + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } in 500..599 -> - throw InternalServerException.builder() - .statusCode(statusCode) - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw InternalServerException.builder() + .statusCode(statusCode) + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } else -> - throw UnexpectedStatusCodeException.builder() - .statusCode(statusCode) - .headers(response.headers()) - .error(errorBodyHandler.handle(response)) - .build() + response.use { + throw UnexpectedStatusCodeException.builder() + .statusCode(statusCode) + .headers(it.headers()) + .error(errorBodyHandler.handle(it)) + .build() + } } } diff --git a/openai-java-core/src/test/kotlin/com/openai/core/handlers/ErrorHandlerTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/handlers/ErrorHandlerTest.kt new file mode 100644 index 000000000..558d3da12 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/core/handlers/ErrorHandlerTest.kt @@ -0,0 +1,85 @@ +package com.openai.core.handlers + +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.http.Headers +import com.openai.core.http.HttpResponse +import com.openai.core.jsonMapper +import com.openai.errors.BadRequestException +import java.io.InputStream +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class ErrorHandlerTest { + + @Test + fun handle_whenSuccessful_doesNotCloseResponse() { + val response = TestHttpResponse(statusCode = 200, body = "") + + val handledResponse = errorHandler(errorBodyHandler(jsonMapper())).handle(response) + + assertThat(handledResponse).isSameAs(response) + assertThat(response.closed).isFalse() + } + + @Test + fun handle_whenStatus400_closesResponseAndThrowsBadRequestException() { + val response = + TestHttpResponse( + statusCode = 400, + body = + """ + {"error":{"code":"code","message":"message","param":"param","type":"type"}} + """ + .trimIndent(), + ) + + val e = + assertThrows { + errorHandler(errorBodyHandler(jsonMapper())).handle(response) + } + + assertThat(response.closed).isTrue() + assertThat(e.body()) + .isEqualTo( + JsonValue.from( + mapOf( + "code" to "code", + "message" to "message", + "param" to "param", + "type" to "type", + ) + ) + ) + } + + @Test + fun handle_whenStatus400WithUnreadableBody_closesResponseAndThrowsBadRequestException() { + val response = TestHttpResponse(statusCode = 400, body = "{not-json") + + val e = + assertThrows { + errorHandler(errorBodyHandler(jsonMapper())).handle(response) + } + + assertThat(response.closed).isTrue() + assertThat(e.body()).isEqualTo(JsonMissing.of()) + } +} + +private class TestHttpResponse(private val statusCode: Int, private val body: String) : + HttpResponse { + + var closed: Boolean = false + + override fun statusCode(): Int = statusCode + + override fun headers(): Headers = Headers.builder().build() + + override fun body(): InputStream = body.byteInputStream() + + override fun close() { + closed = true + } +}