While reading through the http/response package I noticed a few spots carrying more machinery
(or stale documentation) than they need. These are three small, self-contained cleanups in one
subsystem. None of them changes runtime behavior — they reuse stdlib primitives, fix a doc that
describes methods that don't exist, and remove constructor boilerplate that's copy-pasted 18 times.
sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/response/ParsedResponse.kt:55-137 — Replace the bespoke Outcome carrier with a memoized kotlin.Result
The class hand-rolls a private Outcome sealed class (Success/Failure) purely to memoize the
handler's result-or-throw. That's exactly what kotlin.Result is, and the module already uses
runCatching / getOrThrow elsewhere (e.g. http/pipeline/steps/DefaultRetryStep.kt). Storing
the memo as a nullable Result<T>? keeps the deliberate "null success vs. not-yet-parsed"
distinction, and runCatching catches Throwable (not just Exception), so the existing
catch (t: Throwable) semantics — including memoizing an Error so a later call can't re-read the
single-use body — are preserved exactly.
Old code:
// Holds the memoized outcome once the handler has run. A non-null holder means "parsed"
// (success or failure); the wrapped value distinguishes the two. A holder (rather than a
// bare value) lets a legitimately-null success memoize without being mistaken for "unparsed".
@Volatile
private var outcome: Outcome<T>? = null
/** The request that produced [raw]. Does not parse. */
public val request: Request get() = raw.request
/** The negotiated wire protocol. Does not parse. */
public val protocol: Protocol get() = raw.protocol
/** The HTTP status. Does not parse. */
public val status: Status get() = raw.status
/** The status-line reason phrase, or `null` if absent. Does not parse. */
public val message: String? get() = raw.message
/** The response headers. Does not parse. */
public val headers: Headers get() = raw.headers
/**
* Returns the typed value, parsing it on the first call and memoizing the outcome.
*
* The handler runs at most once: the first call invokes [ResponseHandler.handle] (which
* typically consumes and closes the body); subsequent calls return the same value, or
* re-throw the same failure, without re-running the handler.
*
* Any failure the handler throws is memoized and re-thrown verbatim on every later call — not
* just [IOException]. Handlers commonly throw **unchecked** exceptions (the Jackson `jsonHandler`
* throws `SerdeException`), so callers should not assume the only escape is [IOException].
*
* @return The parsed value (which may be `null` if the handler is typed `ResponseHandler<T?>`
* and produces `null`).
* @throws IOException If the handler failed with an [IOException] — cached and re-thrown. The
* `@Throws` declaration covers only the checked surface for Java callers; the handler may also
* propagate **unchecked** exceptions (e.g. `SerdeException` from the Jackson `jsonHandler`),
* which are memoized and re-thrown the same way.
*/
@Throws(IOException::class)
public fun value(): T {
outcome?.let { return it.get() }
return lock.withLock {
outcome?.let { return it.get() }
// Memoize the handler's outcome — success or failure — so neither re-runs the handler
// nor re-reads the (now consumed) body on a subsequent call.
val resolved: Outcome<T> =
try {
Outcome.Success(handler.handle(raw))
} catch (t: Throwable) {
// Catch Throwable, not Exception, on purpose: once the handler has touched the
// single-use body, re-running it would read an already-consumed stream. Even an
// Error (e.g. an OOM mid-parse) is memoized so a later call re-throws it rather
// than re-reading the body and masking the original failure.
Outcome.Failure(t)
}
outcome = resolved
resolved.get()
}
}
/**
* Releases the raw response body. Idempotent (forwards to [Response.close], which is itself
* idempotent). Safe to call whether or not [value] has run.
*
* @throws IOException If the underlying close fails.
*/
@Throws(IOException::class)
override fun close() {
raw.close()
}
private sealed class Outcome<out T> {
abstract fun get(): T
class Success<out T>(private val value: T) : Outcome<T>() {
override fun get(): T = value
}
class Failure(private val error: Throwable) : Outcome<Nothing>() {
override fun get(): Nothing = throw error
}
}
New code:
// Holds the memoized outcome once the handler has run. A non-null Result means "parsed"
// (success or failure); the wrapped value distinguishes the two. A boxed Result (rather than a
// bare value) lets a legitimately-null success memoize without being mistaken for "unparsed".
@Volatile
private var outcome: Result<T>? = null
/** The request that produced [raw]. Does not parse. */
public val request: Request get() = raw.request
/** The negotiated wire protocol. Does not parse. */
public val protocol: Protocol get() = raw.protocol
/** The HTTP status. Does not parse. */
public val status: Status get() = raw.status
/** The status-line reason phrase, or `null` if absent. Does not parse. */
public val message: String? get() = raw.message
/** The response headers. Does not parse. */
public val headers: Headers get() = raw.headers
/**
* Returns the typed value, parsing it on the first call and memoizing the outcome.
*
* The handler runs at most once: the first call invokes [ResponseHandler.handle] (which
* typically consumes and closes the body); subsequent calls return the same value, or
* re-throw the same failure, without re-running the handler.
*
* Any failure the handler throws is memoized and re-thrown verbatim on every later call — not
* just [IOException]. Handlers commonly throw **unchecked** exceptions (the Jackson `jsonHandler`
* throws `SerdeException`), so callers should not assume the only escape is [IOException].
*
* @return The parsed value (which may be `null` if the handler is typed `ResponseHandler<T?>`
* and produces `null`).
* @throws IOException If the handler failed with an [IOException] — cached and re-thrown. The
* `@Throws` declaration covers only the checked surface for Java callers; the handler may also
* propagate **unchecked** exceptions (e.g. `SerdeException` from the Jackson `jsonHandler`),
* which are memoized and re-thrown the same way.
*/
@Throws(IOException::class)
public fun value(): T {
outcome?.let { return it.getOrThrow() }
return lock.withLock {
outcome?.let { return it.getOrThrow() }
// Memoize the outcome (success or failure) so a later call neither re-runs the handler
// nor re-reads the now-consumed body. `runCatching` catches `Throwable`, not just
// `Exception`: re-running a handler that already drained the single-use body would read
// a consumed stream, so even an `Error` (e.g. OOM mid-parse) is memoized and re-thrown.
runCatching { handler.handle(raw) }.also { outcome = it }.getOrThrow()
}
}
/**
* Releases the raw response body. Idempotent (forwards to [Response.close], which is itself
* idempotent). Safe to call whether or not [value] has run.
*
* @throws IOException If the underlying close fails.
*/
@Throws(IOException::class)
override fun close() {
raw.close()
}
Usage — before → after:
val parsed: ParsedResponse<MyDto> = response.parsedWith(jsonHandler)
val dto: MyDto = parsed.value() // parses once, memoizes; later calls return the same value
val parsed: ParsedResponse<MyDto> = response.parsedWith(jsonHandler)
val dto: MyDto = parsed.value() // call sites unchanged — behavior-preserving
Why: kotlin.Result is the standard two-state (success/failure) memo carrier; reusing it deletes
the bespoke Outcome sealed class while runCatching/getOrThrow preserve the exact Throwable-
catching and memoize-before-throw semantics, and a nullable Result<T>? keeps the null-success vs.
unparsed distinction.
API / Build: None — Outcome was private and value()'s signature is unchanged. No apiDump.
Result / runCatching / getOrThrow are in the auto-imported kotlin package, so no imports are
added or removed.
sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/response/ResponseBody.kt:14-45 — Rewrite the class KDoc around the real source() contract and drop the dead InputStream import
The class KDoc describes a byteStream/bytes/string API that does not exist on this type — the
only read accessor is source(): BufferedSource. The doc is the sole reference to java.io.InputStream
in the file, so it's also dragging along an import that the code never uses.
Old code:
import org.dexpace.sdk.core.http.common.MediaType
import org.dexpace.sdk.core.io.BufferedSource
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
/**
* Represents the body of an HTTP response.
*
* A `ResponseBody` provides access to the raw bytes of an HTTP response through [byteStream],
* with convenience methods [bytes] and [string] for common consumption patterns. The body
* **must be closed** after use to release the underlying connection — prefer Kotlin's `use {}`
* or Java's try-with-resources.
*
* This class uses only `java.io` APIs with no external dependencies, making it compatible
* with JDK 8+ and safe to use from platform threads, virtual threads, Kotlin coroutines,
* and reactive schedulers. The underlying [InputStream] performs blocking I/O; callers in
* non-blocking contexts should dispatch to an appropriate scheduler (e.g., `Dispatchers.IO`,
* `Schedulers.boundedElastic()`).
*
* ## Thread safety
*
* Instances are **not** thread-safe. The stream returned by [byteStream] should be read
* from a single thread only. For concurrent access, wrap with
* [LoggableResponseBody] which
* buffers the content and provides thread-safe, repeatable reads.
*
* ## Single-use contract
*
* The base `ResponseBody` can only be read once — [byteStream] returns the same stream on
* every call, and once consumed, the bytes are gone. Use [bytes] or [string] for a
* one-shot read, or wrap with `LoggableResponseBody` for repeatable access.
*
* @see LoggableResponseBody for a buffered wrapper that
* supports repeatable reads and non-destructive logging.
*/
New code:
import org.dexpace.sdk.core.http.common.MediaType
import org.dexpace.sdk.core.io.BufferedSource
import java.io.Closeable
import java.io.IOException
/**
* Represents the body of an HTTP response.
*
* A `ResponseBody` exposes the raw bytes of an HTTP response through a single [source] accessor
* returning a [BufferedSource]. The body **must be closed** after use to release the underlying
* connection — prefer Kotlin's `use {}` or Java's try-with-resources, and close it explicitly even
* when the body is skipped without reading.
*
* This class depends only on the SDK's [BufferedSource] I/O seam with no external dependencies,
* making it compatible with JDK 8+ and safe to use from platform threads, virtual threads, Kotlin
* coroutines, and reactive schedulers. Reading from [source] performs blocking I/O; callers in
* non-blocking contexts should dispatch to an appropriate scheduler (e.g., `Dispatchers.IO`,
* `Schedulers.boundedElastic()`).
*
* ## Thread safety
*
* Instances are **not** thread-safe. The [BufferedSource] returned by [source] should be read
* from a single thread only. For concurrent or repeatable access, wrap with
* [LoggableResponseBody], which buffers the content and provides thread-safe, repeatable reads.
*
* ## Single-use contract
*
* The base `ResponseBody` can only be read once — [source] returns the same [BufferedSource] on
* every call, and once that source is consumed, the bytes are gone. Wrap with
* [LoggableResponseBody] for repeatable access.
*
* @see LoggableResponseBody for a buffered wrapper that
* supports repeatable reads and non-destructive logging.
*/
Usage — before → after:
val body: ResponseBody = ResponseBody.create(source)
body.use { it.source() }
val body: ResponseBody = ResponseBody.create(source)
body.use { it.source() } // call sites unchanged — behavior-preserving
Why: The doc points callers at three methods (byteStream/bytes/string) that the type never
declares; aligning it with the real source(): BufferedSource contract removes the misdirection.
Build: Once the KDoc no longer mentions InputStream, import java.io.InputStream is unused, and
ktlint's no-unused-imports rule (under allWarningsAsErrors) fails the build until it is removed —
so the import removal is required, not optional. No apiDump (no signature change).
sdk-core/.../http/response/exception/HttpException.kt + .../http/response/exception/HttpExceptions.kt:32-400 — Funnel the 18 subclass constructors through one protected base constructor
Every concrete subclass repeats the same six-line block that unpacks response.status /
response.headers / response.body into the base HttpException constructor. Hoisting that into a
single protected base constructor that takes the Response directly lets each subclass collapse to a
one-line delegation. The subclasses' public (response, message?, cause?, value?) surface is unchanged.
Base — HttpException.kt — old code (imports + constructor header):
import org.dexpace.sdk.core.http.common.Headers
import org.dexpace.sdk.core.http.response.ResponseBody
import org.dexpace.sdk.core.http.response.Status
import org.dexpace.sdk.core.io.Buffer
import org.dexpace.sdk.core.util.RetryUtils
import java.io.IOException
public abstract class HttpException
@JvmOverloads
constructor(
public val status: Status,
public val headers: Headers,
public val body: ResponseBody?,
message: String? = null,
cause: Throwable? = null,
public val value: Any? = null,
) : RuntimeException(message ?: defaultMessage(status), cause), Retryable {
/**
* Whether this exception represents a retryable condition. Derived from
* [RetryUtils.isRetryable] over [status]'s code so it can never disagree with the
* live retry policy.
*/
override val isRetryable: Boolean = RetryUtils.isRetryable(status.code)
Base — HttpException.kt — new code (add the Response import + the protected secondary constructor):
import org.dexpace.sdk.core.http.common.Headers
import org.dexpace.sdk.core.http.response.Response
import org.dexpace.sdk.core.http.response.ResponseBody
import org.dexpace.sdk.core.http.response.Status
import org.dexpace.sdk.core.io.Buffer
import org.dexpace.sdk.core.util.RetryUtils
import java.io.IOException
public abstract class HttpException
@JvmOverloads
constructor(
public val status: Status,
public val headers: Headers,
public val body: ResponseBody?,
message: String? = null,
cause: Throwable? = null,
public val value: Any? = null,
) : RuntimeException(message ?: defaultMessage(status), cause), Retryable {
/**
* Convenience constructor that unpacks [status], [headers], and [body] from a [response].
* The per-status subclasses expose a `(response, message?, cause?, value?)` surface and
* delegate here. `protected` so it is reachable only from subclasses.
*/
protected constructor(
response: Response,
message: String?,
cause: Throwable?,
value: Any?,
) : this(response.status, response.headers, response.body, message, cause, value)
/**
* Whether this exception represents a retryable condition. Derived from
* [RetryUtils.isRetryable] over [status]'s code so it can never disagree with the
* live retry policy.
*/
override val isRetryable: Boolean = RetryUtils.isRetryable(status.code)
Subclasses — HttpExceptions.kt — old code (all 18; each KDoc is unchanged and retained):
public open class BadRequestException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class UnauthorizedException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class ForbiddenException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class NotFoundException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class MethodNotAllowedException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class RequestTimeoutException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class ConflictException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class GoneException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class PayloadTooLargeException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class UnsupportedMediaTypeException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class UnprocessableEntityException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class TooManyRequestsException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class InternalServerErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class BadGatewayException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class ServiceUnavailableException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class GatewayTimeoutException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class ClientErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
public open class ServerErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(
status = response.status,
headers = response.headers,
body = response.body,
message = message,
cause = cause,
value = value,
)
Subclasses — HttpExceptions.kt — new code (all 18; each KDoc is unchanged and retained):
public open class BadRequestException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class UnauthorizedException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class ForbiddenException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class NotFoundException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class MethodNotAllowedException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class RequestTimeoutException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class ConflictException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class GoneException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class PayloadTooLargeException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class UnsupportedMediaTypeException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class UnprocessableEntityException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class TooManyRequestsException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class InternalServerErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class BadGatewayException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class ServiceUnavailableException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class GatewayTimeoutException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class ClientErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
public open class ServerErrorException
@JvmOverloads
constructor(
response: Response,
message: String? = null,
cause: Throwable? = null,
value: Any? = null,
) : HttpException(response, message, cause, value)
Usage — before → after:
throw BadRequestException(response)
throw NotFoundException(response, message = "user $id not found")
throw BadRequestException(response)
throw NotFoundException(response, message = "user $id not found") // call sites unchanged — behavior-preserving
Why: Each subclass repeats the same six-argument status = response.status, ... unpacking; hoisting
it into one protected base constructor removes ~7 boilerplate lines per subclass (×18) while leaving the
public constructor surface of every subclass byte-for-byte identical.
API: Adds one protected fun <init> taking (Response, String?, Throwable?, Any?) to
HttpException in sdk-core.api; the 18 subclasses' public constructor entries are unchanged. Run
./gradlew apiDump and commit the regenerated snapshot alongside the change.
While reading through the
http/responsepackage I noticed a few spots carrying more machinery(or stale documentation) than they need. These are three small, self-contained cleanups in one
subsystem. None of them changes runtime behavior — they reuse stdlib primitives, fix a doc that
describes methods that don't exist, and remove constructor boilerplate that's copy-pasted 18 times.
sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/response/ParsedResponse.kt:55-137— Replace the bespokeOutcomecarrier with a memoizedkotlin.ResultThe class hand-rolls a private
Outcomesealed class (Success/Failure) purely to memoize thehandler's result-or-throw. That's exactly what
kotlin.Resultis, and the module already usesrunCatching/getOrThrowelsewhere (e.g.http/pipeline/steps/DefaultRetryStep.kt). Storingthe memo as a nullable
Result<T>?keeps the deliberate "null success vs. not-yet-parsed"distinction, and
runCatchingcatchesThrowable(not justException), so the existingcatch (t: Throwable)semantics — including memoizing anErrorso a later call can't re-read thesingle-use body — are preserved exactly.
Old code:
New code:
Usage — before → after:
Why:
kotlin.Resultis the standard two-state (success/failure) memo carrier; reusing it deletesthe bespoke
Outcomesealed class whilerunCatching/getOrThrowpreserve the exactThrowable-catching and memoize-before-throw semantics, and a nullable
Result<T>?keeps the null-success vs.unparsed distinction.
API / Build: None —
Outcomewasprivateandvalue()'s signature is unchanged. NoapiDump.Result/runCatching/getOrThroware in the auto-importedkotlinpackage, so no imports areadded or removed.
sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/response/ResponseBody.kt:14-45— Rewrite the class KDoc around the realsource()contract and drop the deadInputStreamimportThe class KDoc describes a
byteStream/bytes/stringAPI that does not exist on this type — theonly read accessor is
source(): BufferedSource. The doc is the sole reference tojava.io.InputStreamin the file, so it's also dragging along an import that the code never uses.
Old code:
New code:
Usage — before → after:
Why: The doc points callers at three methods (
byteStream/bytes/string) that the type neverdeclares; aligning it with the real
source(): BufferedSourcecontract removes the misdirection.Build: Once the KDoc no longer mentions
InputStream,import java.io.InputStreamis unused, andktlint's
no-unused-importsrule (underallWarningsAsErrors) fails the build until it is removed —so the import removal is required, not optional. No
apiDump(no signature change).sdk-core/.../http/response/exception/HttpException.kt+.../http/response/exception/HttpExceptions.kt:32-400— Funnel the 18 subclass constructors through one protected base constructorEvery concrete subclass repeats the same six-line block that unpacks
response.status/response.headers/response.bodyinto the baseHttpExceptionconstructor. Hoisting that into asingle
protectedbase constructor that takes theResponsedirectly lets each subclass collapse to aone-line delegation. The subclasses' public
(response, message?, cause?, value?)surface is unchanged.Base —
HttpException.kt— old code (imports + constructor header):Base —
HttpException.kt— new code (add theResponseimport + the protected secondary constructor):Subclasses —
HttpExceptions.kt— old code (all 18; each KDoc is unchanged and retained):Subclasses —
HttpExceptions.kt— new code (all 18; each KDoc is unchanged and retained):Usage — before → after:
Why: Each subclass repeats the same six-argument
status = response.status, ...unpacking; hoistingit into one protected base constructor removes ~7 boilerplate lines per subclass (×18) while leaving the
public constructor surface of every subclass byte-for-byte identical.
API: Adds one
protected fun <init>taking(Response, String?, Throwable?, Any?)toHttpExceptioninsdk-core.api; the 18 subclasses' public constructor entries are unchanged. Run./gradlew apiDumpand commit the regenerated snapshot alongside the change.