Skip to content

Commit

Permalink
Merge pull request #789 from supabase-community/postgrest-exception
Browse files Browse the repository at this point in the history
Add custom exception for PostgREST API errors and include `HttpResponse` in `RestException`s
  • Loading branch information
jan-tennert authored Jan 13, 2025
2 parents 99336ce + e4e9e3a commit c84967a
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -524,16 +524,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)

0 comments on commit c84967a

Please sign in to comment.