supabase-community / postgrest-kt

Postgrest Kotlin Client
53 stars 8 forks source link

Android app INSERT returns http 400 #4

Closed spleenware closed 2 years ago

spleenware commented 2 years ago

I am attempting to use this lib with a Supabase app, just on a simple table to start with and get the hang of it. The select() queries seem to be working fine, is just insert() that is returning the error.

Calling code is like this:

{ private lateinit var app: TestApp private var response: GoTrueTokenResponse? = null private lateinit var client: PostgrestClient

data class SBSimpleTest(
    var id: Long? = null,
    var CreatedAt: Date? = null,
    var Name: String? = null,
    var IntNumber: Int? = null,
    var FloatAmount: Double? = null,
    var IsOK: Boolean? = null,
    var LongNumber: Long? = null,
    var SomeUuid: UUID? = null
)

@Before
fun init() {
    app = ApplicationProvider.getApplicationContext() as TestApp

    response = app.getGoTrueClient().signInWithEmail(" snip  .co", "****").also {
        client = app.obtainPostgresClientFor(it)
    }
}

@Test
fun addARecordTest() {
    val invoice = SBSimpleTest(
        id = -1L,
        CreatedAt = Date(),
        Name = "First Test",
        SomeUuid = UUID.randomUUID(),
        IsOK = true,
        IntNumber = 543,
        LongNumber = 6789L,
        FloatAmount = 34.56
    )
    val count = client.run {
        val response = from<SBSimpleTest>("SimpleTest")
            .insert(value = invoice, returning = Returning.MINIMAL)
            .execute()
        response.count
    } ?: 0L
    assertEquals(1L, count)
}

}

I added some logging to see exactly how the Http request is being formed:

10-15 16:35:14.109 10143 6126 6164 D LoggingHttpClient: uri=https://hrlbnfdxfuhzumhpqiax.supabase.co/rest/v1/SimpleTest?, method=POST 10-15 16:35:14.109 10143 6126 6164 D LoggingHttpClient: edited-uri=https://hrlbnfdxfuhzumhpqiax.supabase.co/rest/v1/SimpleTest 10-15 16:35:14.408 10143 6126 6164 D LoggingHttpClient: body: [{"id":-1,"createdAt":"2021-10-15T05:35:14.086+00:00","floatAmount":34.56,"intNumber":543,"isOK":true,"longNumber":6789,"name":"First Test","someUuid":"ed69d849-00d8-4e69-8a0f-de6f0650869b"}] 10-15 16:35:14.409 10143 6126 6164 D LoggingHttpClient: header: apikey=snip 10-15 16:35:14.409 10143 6126 6164 D LoggingHttpClient: header: Authorization=Bearer snip.F0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.RHeuYYLtlAAHELei-_aPdPypt7H0-GbvUqzkJJamPgU 10-15 16:35:14.410 10143 6126 6164 D LoggingHttpClient: header: Prefer=return=minimal 10-15 16:35:14.412 10143 6126 6164 D LoggingHttpClient: header: Content-Type=application/json 10-15 16:35:14.419 root 1734 6178 E ResolverController: No valid NAT64 prefix (101, /0) 10-15 16:35:14.629 10143 6126 6164 D LoggingHttpClient: status=400, 400 Bad Request HTTP/1.1 10-15 16:35:14.795 10143 6126 6164 E TestRunner: failed: addARecordTest(co... .dbconnector.SimplePostgresTests)

spleenware commented 2 years ago

OK, thanks to Leo, the HTTP response body had the clue needed. Looks like the Jackson ObjectConverter downcases the first letter :-(

10-18 15:38:07.655 10134 9943 9981 D LoggingHttpClient: response: {"hint":null,"message":"column \"createdAt\" of relation \"SimpleTest\" does not exist","code":"42703","details":null}

kevcodez commented 2 years ago

Hi @spleenware - when creating the PostgrestClient, you can instantiate a custom instance, instead of relying on the default Client+Jackson-Converter.

    private val objectMapper = ObjectMapper()
            .registerModule(KotlinModule())
            .registerModule(JavaTimeModule())
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)

class CustomPostgrestJsonConverterJackson : PostgrestJsonConverter {

    override fun serialize(data: Any): String {
        return objectMapper.writeValueAsString(data)
    }

    override fun <T : Any> deserialize(text: String, responseType: Class<T>): T {
        return objectMapper.readValue(text, responseType)
    }

    override fun <T : Any> deserializeList(text: String, responseType: Class<T>): List<T> {
        val javaType = objectMapper.typeFactory
                .constructCollectionType(MutableList::class.java, responseType)

        return objectMapper.readValue(text, javaType)
    }
}
val customJsonConverter = CustomPostgrestJsonConverterJackson()

val myClient = PostgrestClient(
        uri = uri,
        headers = headers,
        schema = schema,
        httpClient = PostgrestHttpClientApache(
                jsonConverter = customJsonConverter,
                httpClient = { HttpClients.createDefault() }
        ),
        jsonConverter = jsonConverter
)

This should allow you to overwrite the default behavior of Jackson. Alternatively, you could probably use annotations on your DTOs, i.e. use @JsonNaming or @JsonProperty annotation.