From 9ea8ffbb9f2382df21feb18541fbefdca4eac034 Mon Sep 17 00:00:00 2001 From: Julian Goede Date: Fri, 15 Sep 2023 14:13:36 +0200 Subject: [PATCH] made errors more expressive --- .../kirc/exception/RegistryClientException.kt | 71 +++++++++++++++---- ...pendingContainerImageRegistryClientImpl.kt | 47 +++++++----- 2 files changed, 88 insertions(+), 30 deletions(-) diff --git a/kirc-core/src/main/kotlin/de/cmdjulian/kirc/exception/RegistryClientException.kt b/kirc-core/src/main/kotlin/de/cmdjulian/kirc/exception/RegistryClientException.kt index 60ea5fe..8504712 100644 --- a/kirc-core/src/main/kotlin/de/cmdjulian/kirc/exception/RegistryClientException.kt +++ b/kirc-core/src/main/kotlin/de/cmdjulian/kirc/exception/RegistryClientException.kt @@ -1,43 +1,86 @@ package de.cmdjulian.kirc.exception -sealed class RegistryClientException(override val message: String, throwable: Throwable? = null) : - RuntimeException(message, throwable) { +import de.cmdjulian.kirc.image.Reference +import de.cmdjulian.kirc.image.Repository +import java.net.URL - sealed class ClientException(message: String, val error: ErrorResponse?, cause: Throwable) : - RegistryClientException(message, cause) { +sealed class RegistryClientException( + val url: URL, + val repository: Repository?, + val reference: Reference?, + override val message: String, + throwable: Throwable? = null, +) : RuntimeException(message, throwable) { - class AuthenticationException(error: ErrorResponse?, cause: Throwable) : - ClientException("authentication required", error, cause) { + sealed class ClientException( + url: URL, + repository: Repository?, + reference: Reference?, + message: String, + val error: ErrorResponse?, + cause: Throwable, + ) : RegistryClientException(url, repository, reference, message, cause) { + + class AuthenticationException( + url: URL, + repository: Repository?, + reference: Reference?, + error: ErrorResponse?, + cause: Throwable, + ) : ClientException(url, repository, reference, "authentication required", error, cause) { override fun toString(): String = "DistributionClientException.AuthenticationError -> $message" } - class AuthorizationException(error: ErrorResponse?, cause: Throwable) : - ClientException("authorization required", error, cause) { + class AuthorizationException( + url: URL, + repository: Repository?, + reference: Reference?, + error: ErrorResponse?, + cause: Throwable, + ) : ClientException(url, repository, reference, "authorization required", error, cause) { override fun toString(): String = "DistributionClientException.AuthorizationError -> $message" } - class NotFoundException(error: ErrorResponse?, cause: Throwable) : ClientException("not found", error, cause) { + class NotFoundException( + url: URL, + repository: Repository?, + reference: Reference?, + error: ErrorResponse?, + cause: Throwable, + ) : ClientException(url, repository, reference, "not found", error, cause) { override fun toString(): String = "DistributionClientException.NotFoundException -> $message" } class MethodNotAllowed( + url: URL, + repository: Repository?, + reference: Reference?, error: ErrorResponse?, cause: Throwable, - ) : ClientException("method not allowed", error, cause) { + ) : ClientException(url, repository, reference, "method not allowed", error, cause) { override fun toString() = "ClientException.MethodNotAllowed (is registry delete enabled?) -> $message" } - class UnexpectedErrorException(error: ErrorResponse?, cause: Throwable) : - ClientException("unknown error returned by server", error, cause) { + class UnexpectedErrorException( + url: URL, + repository: Repository?, + reference: Reference?, + error: ErrorResponse?, + cause: Throwable, + ) : ClientException(url, repository, reference, "unknown error returned by server", error, cause) { override fun toString(): String = "DistributionClientException.UnexpectedErrorException -> $message" } } - class NetworkErrorException(t: Throwable) : RegistryClientException("Network error on registry connect", t) { + class NetworkErrorException(url: URL, repository: Repository?, reference: Reference?, t: Throwable) : + RegistryClientException(url, repository, reference, "Network error on registry connect", t) { + override fun toString(): String = "DistributionClientException.NetworkError -> $message" } - class UnknownErrorException(t: Throwable) : RegistryClientException("An unknown error occurred", t) { + class UnknownErrorException(url: URL, repository: Repository?, reference: Reference?, t: Throwable) : + RegistryClientException(url, repository, reference, "An unknown error occurred", t) { + override fun toString(): String = "DistributionClientException.UnknownError -> $message" } } diff --git a/kirc-suspending/src/main/kotlin/de/cmdjulian/kirc/impl/SuspendingContainerImageRegistryClientImpl.kt b/kirc-suspending/src/main/kotlin/de/cmdjulian/kirc/impl/SuspendingContainerImageRegistryClientImpl.kt index b26ba1c..6c76c0a 100644 --- a/kirc-suspending/src/main/kotlin/de/cmdjulian/kirc/impl/SuspendingContainerImageRegistryClientImpl.kt +++ b/kirc-suspending/src/main/kotlin/de/cmdjulian/kirc/impl/SuspendingContainerImageRegistryClientImpl.kt @@ -47,33 +47,35 @@ internal class SuspendingContainerImageRegistryClientImpl(private val api: Conta override suspend fun tags(repository: Repository, limit: Int?, last: Int?): List = api.tags(repository, limit, last) .map(TagList::tags) - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository, null) } override suspend fun exists(repository: Repository, reference: Reference): Boolean = api.digest(repository, reference) .map { true } - .getOrElse { if (it.response.statusCode == 404) false else throw it.toRegistryClientError() } + .getOrElse { + if (it.response.statusCode == 404) false else throw it.toRegistryClientError(repository, reference) + } override suspend fun manifest(repository: Repository, reference: Reference): Manifest = api.manifests(repository, reference) - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository, reference) } override suspend fun manifestDelete(repository: Repository, reference: Reference): Digest = api.deleteManifest(repository, reference) - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository, reference) } override suspend fun manifestDigest(repository: Repository, reference: Reference): Digest = api.digest(repository, reference) - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository, reference) } override suspend fun blob(repository: Repository, digest: Digest): ByteArray = api.blob(repository, digest) - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository) } override suspend fun config(repository: Repository, reference: Reference): ImageConfig = api.manifest(repository, reference) .map { config(repository, it) } - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository, reference) } override suspend fun config(repository: Repository, manifest: ManifestSingle): ImageConfig = api.blob(repository, manifest.config.digest) @@ -83,7 +85,7 @@ internal class SuspendingContainerImageRegistryClientImpl(private val api: Conta is OciManifestV1 -> jacksonDeserializer().deserialize(config) } } - .getOrElse { throw it.toRegistryClientError() } + .getOrElse { throw it.toRegistryClientError(repository) } override suspend fun toImageClient(repository: Repository, reference: Reference): SuspendingContainerImageClient = when (reference) { @@ -114,14 +116,27 @@ internal class SuspendingContainerImageRegistryClientImpl(private val api: Conta } } -private fun FuelError.toRegistryClientError(): RegistryClientException = when (response.statusCode) { - -1 -> NetworkErrorException(this) - 401 -> AuthenticationException(tryOrNull { JsonMapper.readValue(response.data) }, this) - 403 -> AuthorizationException(tryOrNull { JsonMapper.readValue(response.data) }, this) - 404 -> NotFoundException(tryOrNull { JsonMapper.readValue(response.data) }, this) - 405 -> MethodNotAllowed(tryOrNull { JsonMapper.readValue(response.data) }, this) - in 406..499 -> UnexpectedErrorException(tryOrNull { JsonMapper.readValue(response.data) }, this) - else -> UnknownErrorException(this) +private fun FuelError.toRegistryClientError( + repository: Repository? = null, + reference: Reference? = null, +): RegistryClientException { + val url = this.response.url + val data = response.data + return when (response.statusCode) { + -1 -> NetworkErrorException(url, repository, reference, this) + + 401 -> AuthenticationException(url, repository, reference, tryOrNull { JsonMapper.readValue(data) }, this) + + 403 -> AuthorizationException(url, repository, reference, tryOrNull { JsonMapper.readValue(data) }, this) + + 404 -> NotFoundException(url, repository, reference, tryOrNull { JsonMapper.readValue(data) }, this) + + 405 -> MethodNotAllowed(url, repository, reference, tryOrNull { JsonMapper.readValue(data) }, this) + in 406..499 -> + UnexpectedErrorException(url, repository, reference, tryOrNull { JsonMapper.readValue(data) }, this) + + else -> UnknownErrorException(url, repository, reference, this) + } } private inline fun tryOrNull(block: () -> T): T? = runCatching(block).getOrNull()