From f1b88678bc3f502a1f7e8e098647e630f06ea3d9 Mon Sep 17 00:00:00 2001 From: Philip Wedemann <22521688+hfhbd@users.noreply.github.com> Date: Sun, 16 May 2021 23:24:21 +0200 Subject: [PATCH] Nullable record if recordName not found (#51) Co-authored-by: hfhbd --- .../api/cloudkitclient-core.api | 73 +++++++++++++++++++ .../app/softwork/cloudkitclient/CKClient.kt | 65 +++++++++++++---- .../app/softwork/cloudkitclient/Client.kt | 6 +- .../app/softwork/cloudkitclient/Error.kt | 13 ++++ .../app/softwork/cloudkitclient/Response.kt | 2 +- .../app/softwork/cloudkitclient/Storage.kt | 4 +- .../softwork/cloudkitclient/TestDatabase.kt | 6 +- .../softwork/cloudkitclient/TodoClientTest.kt | 5 ++ 8 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Error.kt diff --git a/cloudkitclient-core/api/cloudkitclient-core.api b/cloudkitclient-core/api/cloudkitclient-core.api index cec9165..f064d5e 100644 --- a/cloudkitclient-core/api/cloudkitclient-core.api +++ b/cloudkitclient-core/api/cloudkitclient-core.api @@ -136,6 +136,47 @@ public final class app/softwork/cloudkitclient/Environment$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class app/softwork/cloudkitclient/Error : java/lang/Exception { + public static final field Companion Lapp/softwork/cloudkitclient/Error$Companion; + public fun ()V + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)Lapp/softwork/cloudkitclient/Error; + public static synthetic fun copy$default (Lapp/softwork/cloudkitclient/Error;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lapp/softwork/cloudkitclient/Error; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()Ljava/lang/String; + public final fun getRecordName ()Ljava/lang/String; + public final fun getRedirectURL ()Ljava/lang/String; + public final fun getRetryAfter ()Ljava/lang/Integer; + public final fun getServerErrorCode ()Ljava/lang/String; + public final fun getUuid ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class app/softwork/cloudkitclient/Error$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lapp/softwork/cloudkitclient/Error$$serializer; + public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/softwork/cloudkitclient/Error; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/softwork/cloudkitclient/Error;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class app/softwork/cloudkitclient/Error$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class app/softwork/cloudkitclient/Filter { public static final field Companion Lapp/softwork/cloudkitclient/Filter$Companion; public synthetic fun (ILjava/lang/String;Lapp/softwork/cloudkitclient/Filter$Comparator;Lapp/softwork/cloudkitclient/values/Value;Ljava/lang/Double;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V @@ -432,6 +473,38 @@ public abstract interface class app/softwork/cloudkitclient/Record$Information { public abstract fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class app/softwork/cloudkitclient/Response { + public static final field Companion Lapp/softwork/cloudkitclient/Response$Companion; + public synthetic fun (ILjava/util/List;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/lang/String;)Lapp/softwork/cloudkitclient/Response; + public static synthetic fun copy$default (Lapp/softwork/cloudkitclient/Response;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lapp/softwork/cloudkitclient/Response; + public fun equals (Ljava/lang/Object;)Z + public final fun getContinuationMarker ()Ljava/lang/String; + public final fun getRecords ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class app/softwork/cloudkitclient/Response$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lapp/softwork/cloudkitclient/Response; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lapp/softwork/cloudkitclient/Response;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class app/softwork/cloudkitclient/Response$Companion { + public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +} + public final class app/softwork/cloudkitclient/Sort { public static final field Companion Lapp/softwork/cloudkitclient/Sort$Companion; public synthetic fun (ILjava/lang/String;ZLapp/softwork/cloudkitclient/types/Location;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V diff --git a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/CKClient.kt b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/CKClient.kt index f9d97b7..d06d8a5 100644 --- a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/CKClient.kt +++ b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/CKClient.kt @@ -45,10 +45,10 @@ public class CKClient( public inner class Database internal constructor(internal val name: String) : Client.Database { public override suspend fun > query( - recordInformation: Record.Information, + recordInformation: Information, zoneID: ZoneID, - filter: Filter.Builder.() -> Unit, - sort: Sort.Builder.() -> Unit + sort: Sort.Builder.() -> Unit, + filter: Filter.Builder.() -> Unit ): List = request("/records/query", Request.serializer()) { Request( zoneID = zoneID, @@ -62,7 +62,7 @@ public class CKClient( public override suspend fun > create( record: R, - recordInformation: Record.Information + recordInformation: Information ): R = request( "/records/modify", @@ -79,13 +79,20 @@ public class CKClient( recordName: String, recordInformation: Information, zoneID: ZoneID - ): R = request("/records/lookup", Request.RecordLookup.serializer()) { + ): R? = request("/records/lookup", Request.RecordLookup.serializer()) { Request.RecordLookup(listOf(Request.RecordLookup.RecordName(recordName = recordName)), zoneID = zoneID) - }.toRegularResponse(recordInformation).first() + }.handleError(recordInformation).run { + when (this) { + is Holder.Success -> response.records.single() + is Holder.Failure -> { + if (error.serverErrorCode == "NOT_FOUND") null else throw error + } + } + } public override suspend fun > update( record: R, - recordInformation: Record.Information + recordInformation: Information ): R = request( "/records/modify", @@ -100,7 +107,7 @@ public class CKClient( public override suspend fun > delete( record: R, - recordInformation: Record.Information + recordInformation: Information ) { request( "/records/modify", @@ -116,7 +123,7 @@ public class CKClient( override suspend fun > upload( asset: ByteArray, - recordInformation: Record.Information, + recordInformation: Information, field: KProperty1, recordName: String?, zoneID: ZoneID @@ -167,14 +174,42 @@ public class CKClient( this.body = body } - private fun > String.toRegularResponse(recordInformation: Record.Information) = - let { - println(it) - json.decodeFromString( + private fun > String.toRegularResponse(recordInformation: Information): List { + val response = json.decodeFromString( + Response.serializer( + recordInformation.fieldsSerializer(), + recordInformation.serializer() + ), this + ) + return response.records + } + + + private fun > String.handleError( + recordInformation: Information + ): Holder = + try { + val response = json.decodeFromString( Response.serializer( recordInformation.fieldsSerializer(), recordInformation.serializer() - ), it + ), this ) - }.records + Holder.Success(response) + } catch (e: SerializationException) { + try { + val error = json.decodeFromString(Holder.Failure.ReadError.serializer(), this) + Holder.Failure(error.records.first()) + } catch (error: SerializationException) { + throw error + } + } + + private sealed class Holder> { + class Success>(val response: Response) : Holder() + class Failure>(val error: Error) : Holder() { + @Serializable + data class ReadError(val records: List) + } + } } diff --git a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Client.kt b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Client.kt index eb7bffa..37a4e1a 100644 --- a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Client.kt +++ b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Client.kt @@ -17,8 +17,8 @@ public interface Client { public suspend fun > query( recordInformation: Information, zoneID: ZoneID = ZoneID.default, - filter: Filter.Builder.() -> Unit = { }, - sort: Builder.() -> Unit = { } + sort: Builder.() -> Unit = { }, + filter: Filter.Builder.() -> Unit = { } ): List public suspend fun > create(record: R, recordInformation: Information): R @@ -26,7 +26,7 @@ public interface Client { recordName: String, recordInformation: Information, zoneID: ZoneID = ZoneID.default - ): R + ): R? public suspend fun > update(record: R, recordInformation: Information): R public suspend fun > delete(record: R, recordInformation: Information) diff --git a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Error.kt b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Error.kt new file mode 100644 index 0000000..0a57e6d --- /dev/null +++ b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Error.kt @@ -0,0 +1,13 @@ +package app.softwork.cloudkitclient + +import kotlinx.serialization.Serializable + +@Serializable +public data class Error( + val recordName: String? = null, + val reason: String? = null, + val serverErrorCode: String? = null, + val retryAfter: Int? = null, + val uuid: String? = null, + val redirectURL: String? = null, +) : Exception() diff --git a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Response.kt b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Response.kt index 9df90f8..5b6907d 100644 --- a/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Response.kt +++ b/cloudkitclient-core/src/commonMain/kotlin/app/softwork/cloudkitclient/Response.kt @@ -3,7 +3,7 @@ package app.softwork.cloudkitclient import kotlinx.serialization.* @Serializable -internal data class Response>( +public data class Response>( val records: List, val continuationMarker: String? = null ) diff --git a/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/Storage.kt b/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/Storage.kt index 09303af..0de15cf 100644 --- a/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/Storage.kt +++ b/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/Storage.kt @@ -48,8 +48,8 @@ public class Storage( public fun > get( recordName: String, recordInformation: Record.Information - ): R { - return storage[recordName]!! as R + ): R? { + return storage[recordName]?.let { it as R } } public fun > delete(record: R, recordInformation: Information) { diff --git a/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/TestDatabase.kt b/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/TestDatabase.kt index dd5b616..b3d0ae7 100644 --- a/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/TestDatabase.kt +++ b/cloudkitclient-testing/src/commonMain/kotlin/app/softwork/cloudkitclient/TestDatabase.kt @@ -22,8 +22,8 @@ public open class TestDatabase( public override suspend fun > query( recordInformation: Information, zoneID: ZoneID, - filter: Filter.Builder.() -> Unit, - sort: Sort.Builder.() -> Unit + sort: Sort.Builder.() -> Unit, + filter: Filter.Builder.() -> Unit ): List { return zones[zoneID]!!.query(recordInformation, Filter.Builder().apply(filter).build(), Sort.Builder().apply(sort).build()) } @@ -37,7 +37,7 @@ public open class TestDatabase( recordName: String, recordInformation: Information, zoneID: ZoneID - ): R = zones[zoneID]!!.get(recordName, recordInformation) + ): R? = zones[zoneID]!!.get(recordName, recordInformation) override suspend fun > update(record: R, recordInformation: Information): R = record.zone.update(record, recordInformation) diff --git a/integrationTest/src/commonTest/kotlin/app/softwork/cloudkitclient/TodoClientTest.kt b/integrationTest/src/commonTest/kotlin/app/softwork/cloudkitclient/TodoClientTest.kt index c316642..001d26b 100644 --- a/integrationTest/src/commonTest/kotlin/app/softwork/cloudkitclient/TodoClientTest.kt +++ b/integrationTest/src/commonTest/kotlin/app/softwork/cloudkitclient/TodoClientTest.kt @@ -62,5 +62,10 @@ class TodoClientTest { client.publicDB.delete(todoDeletedAsset, TodoRecord) } + @Test + fun notFound() = runTest(clients) { client -> + assertNull(client.publicDB.read("TestingNotFound", TodoRecord)) + } + private val Client.timeout get() = if (this is TestClient) 0L else 2500L }