From 37b0b835d440e58afaf76a18c65741200ac5ea48 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Fri, 4 Oct 2024 17:17:31 +0200 Subject: [PATCH] [CustomerCenter] Fix help path deserializing when unknown type (#1869) ### Description While testing one thing I noticed we were not handling unknown help path types. This basically caused the whole screen to not be deserialized, since we catch deserialization issues at that level, but we can do better than that and just not deserialize the unknown path type. --- .../CustomerCenterConfigData.kt | 2 +- .../customercenter/HelpPathsSerializer.kt | 40 +++++++++++++++++++ .../CustomerCenterConfigDataTest.kt | 28 ++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/HelpPathsSerializer.kt diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/CustomerCenterConfigData.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/CustomerCenterConfigData.kt index f9524b74c8..91a39b9fb6 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/CustomerCenterConfigData.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/CustomerCenterConfigData.kt @@ -258,7 +258,7 @@ data class CustomerCenterConfigData( val type: ScreenType, val title: String, @Serializable(with = EmptyStringToNullSerializer::class) val subtitle: String? = null, - val paths: List, + @Serializable(with = HelpPathsSerializer::class) val paths: List, ) { @Serializable enum class ScreenType { diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/HelpPathsSerializer.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/HelpPathsSerializer.kt new file mode 100644 index 0000000000..8bcf810b54 --- /dev/null +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/customercenter/HelpPathsSerializer.kt @@ -0,0 +1,40 @@ +package com.revenuecat.purchases.customercenter + +import com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI +import com.revenuecat.purchases.common.debugLog +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.jsonArray + +@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class) +internal object HelpPathsSerializer : KSerializer> { + override val descriptor: SerialDescriptor = ListSerializer( + CustomerCenterConfigData.HelpPath.serializer(), + ).descriptor + + override fun deserialize(decoder: Decoder): List { + val list = mutableListOf() + val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") + val jsonArray = jsonInput.decodeJsonElement().jsonArray + + jsonArray.forEach { jsonElement -> + try { + list.add( + jsonInput.json.decodeFromJsonElement(CustomerCenterConfigData.HelpPath.serializer(), jsonElement), + ) + } catch (e: IllegalArgumentException) { + debugLog("Issue deserializing CustomerCenter HelpPath. Ignoring. Error: $e") + } + } + + return list + } + + override fun serialize(encoder: Encoder, value: List) { + ListSerializer(CustomerCenterConfigData.HelpPath.serializer()).serialize(encoder, value) + } +} diff --git a/purchases/src/test/java/com/revenuecat/purchases/customercenter/CustomerCenterConfigDataTest.kt b/purchases/src/test/java/com/revenuecat/purchases/customercenter/CustomerCenterConfigDataTest.kt index e85014f4ec..dec3d8877c 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/customercenter/CustomerCenterConfigDataTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/customercenter/CustomerCenterConfigDataTest.kt @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI import com.revenuecat.purchases.common.Backend import org.assertj.core.api.Assertions.assertThat +import org.json.JSONObject import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -144,10 +145,33 @@ class CustomerCenterConfigDataTest { assertThat(support.email).isEqualTo("support@example.com") } - private fun createSampleConfigData(): CustomerCenterConfigData { + @Test + fun `can parse json with unknown screen types`() { + val json = JSONObject(loadTestJSON()) + val screens = json.getJSONObject("customer_center").getJSONObject("screens") + screens.put("random_screen_id", screens.getJSONObject("MANAGEMENT")) + val configData = createSampleConfigData(json.toString()) + assertThat(configData.screens).hasSize(2) + } + + @Test + fun `can parse json with unknown path types`() { + val json = JSONObject(loadTestJSON()) + val managementScreenPaths = json + .getJSONObject("customer_center") + .getJSONObject("screens") + .getJSONObject("MANAGEMENT") + .getJSONArray("paths") + val firstPathClone = JSONObject(managementScreenPaths.getJSONObject(0).toString()) + managementScreenPaths.put(firstPathClone.put("type", "UNKNOWN_PATH_TYPE")) + val configData = createSampleConfigData(json.toString()) + assertThat(configData.screens[CustomerCenterConfigData.Screen.ScreenType.MANAGEMENT]!!.paths).hasSize(4) + } + + private fun createSampleConfigData(json: String = loadTestJSON()): CustomerCenterConfigData { return Backend.json.decodeFromString( CustomerCenterRoot.serializer(), - loadTestJSON() + json, ).customerCenter }