[2.x] fix: Display HTTP response body when bundle upload fails (#8630)

When a bundle upload to Central Portal fails, the error now displays the HTTP response body instead of just the status code. This provides more useful debugging information, as the response body typically contains detailed error messages from the server.
This commit is contained in:
DEBORAH FUNMILOLA OLABOYE 2026-01-28 16:36:45 +01:00 committed by GitHub
parent aa4ac5b981
commit a66a3064f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 1 deletions

View File

@ -82,7 +82,7 @@ class SonaClient(reqTransform: Request => Request, uploadRequestTimeout: FiniteD
)
)
.withRequestTimeout(uploadRequestTimeout)
http.run(reqTransform(req), Gigahorse.asString)
http.run(reqTransform(req), SonaClient.asStringWithErrorBody)
}
awaitWithMessage(res, "uploading...", log, totalAwaitDuration)
}
@ -200,6 +200,17 @@ object SonaClient {
Parser.parseFromByteBuffer(r.bodyAsByteBuffer).get
def as[A1: JsonFormat]: FullResponse => A1 = asJson.andThen(Converter.fromJsonUnsafe[A1])
val asPublisherStatus: FullResponse => PublisherStatus = as[PublisherStatus]
/**
* Response handler that returns the body as a String on success (2xx status),
* or throws a [[SonaStatusError]] with both the status code and response body on failure.
* This provides more detailed error information than [[gigahorse.StatusError]].
*/
val asStringWithErrorBody: FullResponse => String = { response =>
val body = response.bodyAsString
if (response.status >= 200 && response.status < 300) body
else throw new SonaStatusError(response.status, body)
}
def oauthClient(
userName: String,
userToken: String,
@ -270,3 +281,17 @@ object PublishingType {
case object Automatic extends PublishingType
case object UserManaged extends PublishingType
}
/**
* Exception thrown when an HTTP request to the Sonatype API fails with a non-2xx status.
* Unlike [[gigahorse.StatusError]], this exception includes the response body which
* typically contains useful error details from the server.
*
* @param status the HTTP status code
* @param body the response body content
*/
class SonaStatusError(val status: Int, val body: String)
extends RuntimeException(
if (body.nonEmpty) s"Unexpected status: $status\n$body"
else s"Unexpected status: $status"
)

View File

@ -17,6 +17,21 @@ import scala.collection.immutable
object SonaClientTest extends BasicTestSuite:
test("SonaStatusError should include both status and body in message"):
val error = new SonaStatusError(401, "Invalid token")
assert(error.status == 401)
assert(error.body == "Invalid token")
assert(error.getMessage == "Unexpected status: 401\nInvalid token")
test("SonaStatusError should handle empty body"):
val error = new SonaStatusError(500, "")
assert(error.getMessage == "Unexpected status: 500")
test("SonaStatusError should preserve multiline error body"):
val body = """{"error": "Unauthorized", "message": "Invalid credentials"}"""
val error = new SonaStatusError(401, body)
assert(error.getMessage.contains(body))
private def doTest(
errorsJsonText: Option[String],
expectedErrorMessage: String,