Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom exception for PostgREST API errors and include HttpResponse in RestExceptions #789

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -522,16 +522,16 @@ internal class AuthImpl(

private fun checkErrorCodes(error: GoTrueErrorResponse, response: HttpResponse): RestException? {
return when (error.error) {
AuthWeakPasswordException.CODE -> AuthWeakPasswordException(error.description, response.status.value, error.weakPassword?.reasons ?: emptyList())
AuthWeakPasswordException.CODE -> AuthWeakPasswordException(error.description, response, error.weakPassword?.reasons ?: emptyList())
AuthSessionMissingException.CODE -> {
authScope.launch {
Auth.logger.e { "Received session not found api error. Clearing session..." }
clearSession()
}
AuthSessionMissingException(response.status.value)
AuthSessionMissingException(response)
}
else -> {
error.error?.let { AuthRestException(it, error.description, response.status.value) }
error.error?.let { AuthRestException(it, error.description, response) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package io.github.jan.supabase.auth.exception

import io.github.jan.supabase.exceptions.RestException
import io.ktor.client.statement.HttpResponse

/**
* Base class for rest exceptions thrown by the Auth API.
* @property errorCode The error code of the rest exception. This should be a known [AuthErrorCode]. If it is not, use [error] instead.
* @param message The message of the rest exception.
* @param errorDescription The description of the error.
*/
open class AuthRestException(errorCode: String, message: String, statusCode: Int): RestException(
open class AuthRestException(errorCode: String, val errorDescription: String, response: HttpResponse): RestException(
error = errorCode,
description = "Auth API error: $errorCode",
message = message,
statusCode = statusCode
description = "$errorDescription: $errorCode",
response = response
) {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.github.jan.supabase.auth.exception

import io.ktor.client.statement.HttpResponse

/**
* Exception thrown when a session is not found.
*/
class AuthSessionMissingException(statusCode: Int): AuthRestException(
class AuthSessionMissingException(response: HttpResponse): AuthRestException(
errorCode = CODE,
statusCode = statusCode,
message = "Session not found. This can happen if the user was logged out or deleted."
response = response,
errorDescription = "Session not found. This can happen if the user was logged out or deleted."
) {

internal companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package io.github.jan.supabase.auth.exception

import io.ktor.client.statement.HttpResponse

/**
* Exception thrown on sign-up if the password is too weak
* @param description The description of the exception.
* @param reasons The reasons why the password is weak.
*/
class AuthWeakPasswordException(
description: String,
statusCode: Int,
response: HttpResponse,
val reasons: List<String>
) : AuthRestException(
CODE,
description,
statusCode
response
) {

internal companion object {
Expand Down
4 changes: 2 additions & 2 deletions Auth/src/commonTest/kotlin/AuthRestExceptionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class AuthRestExceptionTest {
}
}
assertEquals("error_code", exception.error)
assertEquals("error_message", exception.message)
assertEquals("error_message", exception.errorDescription)
}
}

Expand Down Expand Up @@ -83,7 +83,7 @@ class AuthRestExceptionTest {
}
}
assertEquals("weak_password", exception.error)
assertEquals("error_message", exception.message)
assertEquals("error_message", exception.errorDescription)
assertEquals(listOf("reason1", "reason2"), exception.reasons)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package io.github.jan.supabase.postgrest

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.SupabaseSerializer
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.exceptions.HttpRequestException
import io.github.jan.supabase.logging.SupabaseLogger
import io.github.jan.supabase.plugins.CustomSerializationConfig
import io.github.jan.supabase.plugins.CustomSerializationPlugin
import io.github.jan.supabase.plugins.MainConfig
import io.github.jan.supabase.plugins.MainPlugin
import io.github.jan.supabase.plugins.SupabasePluginProvider
import io.github.jan.supabase.postgrest.exception.PostgrestRestException
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder
import io.github.jan.supabase.postgrest.query.PostgrestUpdate
import io.github.jan.supabase.postgrest.query.request.RpcRequestBuilder
import io.github.jan.supabase.postgrest.result.PostgrestResult
import io.ktor.client.plugins.HttpRequestTimeoutException
import kotlinx.serialization.json.JsonObject

/**
Expand Down Expand Up @@ -66,7 +68,9 @@ sealed interface Postgrest : MainPlugin<Postgrest.Config>, CustomSerializationPl
*
* @param function The name of the function
* @param request Filter the result
* @throws RestException or one of its subclasses if the request failed
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend fun rpc(
function: String,
Expand All @@ -79,7 +83,9 @@ sealed interface Postgrest : MainPlugin<Postgrest.Config>, CustomSerializationPl
* @param function The name of the function
* @param parameters The parameters for the function
* @param request Filter the result
* @throws RestException or one of its subclasses if the request failed
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend fun rpc(
function: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@ import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.auth.authenticatedSupabaseApi
import io.github.jan.supabase.bodyOrNull
import io.github.jan.supabase.exceptions.BadRequestRestException
import io.github.jan.supabase.exceptions.NotFoundRestException
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.exceptions.UnauthorizedRestException
import io.github.jan.supabase.exceptions.UnknownRestException
import io.github.jan.supabase.postgrest.exception.PostgrestRestException
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder
import io.github.jan.supabase.postgrest.query.request.RpcRequestBuilder
import io.github.jan.supabase.postgrest.request.RpcRequest
import io.github.jan.supabase.postgrest.result.PostgrestResult
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
import kotlinx.serialization.json.JsonObject

internal class PostgrestImpl(override val supabaseClient: SupabaseClient, override val config: Postgrest.Config) : Postgrest {
Expand Down Expand Up @@ -48,12 +44,7 @@ internal class PostgrestImpl(override val supabaseClient: SupabaseClient, overri

override suspend fun parseErrorResponse(response: HttpResponse): RestException {
val body = response.bodyOrNull<PostgrestErrorResponse>() ?: PostgrestErrorResponse("Unknown error")
return when(response.status) {
HttpStatusCode.Unauthorized -> UnauthorizedRestException(body.message, response, body.details ?: body.hint)
HttpStatusCode.NotFound -> NotFoundRestException(body.message, response, body.details ?: body.hint)
HttpStatusCode.BadRequest -> BadRequestRestException(body.message, response, body.details ?: body.hint)
else -> UnknownRestException(body.message, response, body.details ?: body.hint)
}
return PostgrestRestException(body.message, body.hint, body.details, body.code, response)
}

override suspend fun rpc(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.github.jan.supabase.postgrest.exception

import io.github.jan.supabase.exceptions.RestException
import io.ktor.client.statement.HttpResponse

/**
* Exception thrown when a Postgrest request fails
* @param message The error message
* @param hint A hint to the error
* @param details Additional details about the error
* @param code The error code
* @param response The response that caused the exception
*/
class PostgrestRestException(
message: String,
val hint: String?,
val details: String?,
val code: String?,
response: HttpResponse
): RestException(message, hint ?: details, response)
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package io.github.jan.supabase.postgrest.query
import io.github.jan.supabase.auth.PostgrestFilterDSL
import io.github.jan.supabase.encodeToJsonElement
import io.github.jan.supabase.exceptions.HttpRequestException
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.exception.PostgrestRestException
import io.github.jan.supabase.postgrest.executor.RestRequestExecutor
import io.github.jan.supabase.postgrest.mapToFirstValue
import io.github.jan.supabase.postgrest.query.request.InsertRequestBuilder
Expand Down Expand Up @@ -35,7 +35,7 @@ class PostgrestQueryBuilder(
* @param columns The columns to retrieve, defaults to [Columns.ALL]. You can also use [Columns.list], [Columns.type] or [Columns.raw] to specify the columns
* @param request Additional configurations for the request including filters
* @return PostgrestResult which is either an error, an empty JsonArray or the data you requested as an JsonArray
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand Down Expand Up @@ -67,7 +67,7 @@ class PostgrestQueryBuilder(
*
* @param values The values to insert, will automatically get serialized into json.
* @param request Additional configurations for the request including filters
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand Down Expand Up @@ -107,7 +107,7 @@ class PostgrestQueryBuilder(
*
* @param value The value to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand All @@ -121,7 +121,7 @@ class PostgrestQueryBuilder(
*
* @param values The values to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand Down Expand Up @@ -150,7 +150,7 @@ class PostgrestQueryBuilder(
*
* @param value The value to insert, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand All @@ -166,7 +166,7 @@ class PostgrestQueryBuilder(
*
* @param update Specifies the fields to update via a DSL
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand All @@ -193,7 +193,7 @@ class PostgrestQueryBuilder(
*
* @param value The value to update, will automatically get serialized into json.
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand All @@ -219,7 +219,7 @@ class PostgrestQueryBuilder(
* By default, deleted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`.
*
* @param request Additional filtering to apply to the query
* @throws RestException or one of its subclasses if receiving an error response
* @throws PostgrestRestException if receiving an error response
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.github.jan.supabase.postgrest.query.request

import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.PropertyConversionMethod
import io.github.jan.supabase.postgrest.RpcMethod
import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder

/**
* Request builder for [Postgrest.rpcRequest]
* Request builder for [Postgrest.rpc]
*/
class RpcRequestBuilder(defaultSchema: String, propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,42 @@ import io.ktor.client.statement.request
* Plugins may extend this class to provide more specific exceptions
* @param error The error returned by Supabase
* @param description The error description returned by Supabase
* @param statusCode The HTTP status code of the rest exception.
* @param response The response that caused the exception
* @see UnauthorizedRestException
* @see BadRequestRestException
* @see NotFoundRestException
* @see UnknownRestException
*/
open class RestException(val error: String, val description: String?, val statusCode: Int, message: String): Exception(message) {

constructor(error: String, response: HttpResponse, message: String? = null): this(error, message, response.status.value, """
$error${message?.let { " ($it)" } ?: ""}
open class RestException(val error: String, val description: String?, val response: HttpResponse): Exception("""
$error${description?.let { " ($it)" } ?: ""}
URL: ${response.request.url}
Headers: ${response.request.headers.entries()}
Http Method: ${response.request.method.value}
""".trimIndent())
""".trimIndent()) {

/**
* The status code of the response
*/
val statusCode = response.status.value

}

/**
* Thrown when supabase-kt receives a response indicating an authentication error
*/
class UnauthorizedRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, response, message)
class UnauthorizedRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, message, response)

/**
* Thrown when supabase-kt receives a response indicating that the request was invalid due to missing or wrong fields
*/
class BadRequestRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, response, message)
class BadRequestRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, message, response)

/**
* Thrown when supabase-kt receives a response indicating that the wanted resource was not found
*/
class NotFoundRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, response, message)
class NotFoundRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, message, response)

/**
* Thrown for all other response codes
*/
class UnknownRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, response, message)
class UnknownRestException(error: String, response: HttpResponse, message: String? = null): RestException(error, message, response)