diff --git a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadata.kt b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadata.kt index ebb59c444..2f2d1980d 100644 --- a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadata.kt +++ b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadata.kt @@ -1,12 +1,29 @@ package com.pubnub.api.models.consumer.objects.channel +import com.pubnub.api.utils.PatchValue + data class PNChannelMetadata( val id: String, - val name: String?, - val description: String?, - val custom: Map?, - val updated: String?, - val eTag: String?, - val type: String?, - val status: String?, -) + val name: PatchValue? = null, + val description: PatchValue? = null, + val custom: PatchValue?>? = null, + val updated: PatchValue? = null, + val eTag: PatchValue? = null, + val type: PatchValue? = null, + val status: PatchValue? = null, +) { + /** + * Merge information from this `PNChannelMetadata` with new data from `update`, returning a new `PNChannelMetadata` instance. + */ + operator fun plus(update: PNChannelMetadata): PNChannelMetadata { + return copy( + name = update.name ?: name, + description = update.description ?: description, + custom = update.custom ?: custom, + updated = update.updated ?: updated, + eTag = update.eTag ?: eTag, + type = update.type ?: type, + status = update.status ?: status, + ) + } +} diff --git a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataResult.kt b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataResult.kt index a2dc22110..771bec6f1 100644 --- a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataResult.kt +++ b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataResult.kt @@ -2,5 +2,5 @@ package com.pubnub.api.models.consumer.objects.channel data class PNChannelMetadataResult( val status: Int, - val data: PNChannelMetadata?, + val data: PNChannelMetadata, ) diff --git a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadata.kt b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadata.kt index 852e37412..a1379b325 100644 --- a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadata.kt +++ b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadata.kt @@ -1,14 +1,34 @@ package com.pubnub.api.models.consumer.objects.uuid +import com.pubnub.api.utils.PatchValue + +// TODO add a test that checks sending patch values to server and reading them back when we have the "sending" part data class PNUUIDMetadata( val id: String, - val name: String?, - val externalId: String?, - val profileUrl: String?, - val email: String?, - val custom: Map?, - val updated: String?, - val eTag: String?, - val type: String?, - val status: String?, -) + val name: PatchValue? = null, + val externalId: PatchValue? = null, + val profileUrl: PatchValue? = null, + val email: PatchValue? = null, + val custom: PatchValue?>? = null, + val updated: PatchValue? = null, + val eTag: PatchValue? = null, + val type: PatchValue? = null, + val status: PatchValue? = null, +) { + /** + * Merge information from this `PNUUIDMetadata` with new data from `update`, returning a new `PNUUIDMetadata` instance. + */ + operator fun plus(update: PNUUIDMetadata): PNUUIDMetadata { + return copy( + name = update.name ?: name, + externalId = update.externalId ?: externalId, + profileUrl = update.profileUrl ?: profileUrl, + email = update.email ?: email, + custom = update.custom ?: custom, + updated = update.updated ?: updated, + eTag = update.eTag ?: eTag, + type = update.type ?: type, + status = update.status ?: status, + ) + } +} diff --git a/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/utils/PatchValue.kt b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/utils/PatchValue.kt new file mode 100644 index 000000000..c60d7b801 --- /dev/null +++ b/pubnub-core/pubnub-core-api/src/commonMain/kotlin/com/pubnub/api/utils/PatchValue.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.utils + +/** + * An optional that accepts nullable values. Thus, it can represent two (`PatchValue`) or three (`PatchValue?`) states: + * * `PatchValue.of(someValue)` - value is present and that value is `someValue` + * * `PatchValue.of(null)` - value is present and that value is `null` + * * `null` - lack of information about value (no update for this field) + */ +data class PatchValue internal constructor(val value: T) { + companion object { + /** + * Create an optional with the specified value (which can be null). + */ + @JvmStatic + fun of(value: T): PatchValue = PatchValue(value) + } +} diff --git a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/PubNubCore.kt b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/PubNubCore.kt index 142783209..b6a4b4751 100644 --- a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/PubNubCore.kt +++ b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/PubNubCore.kt @@ -298,7 +298,6 @@ class PubNubCore internal constructor( message: Any, meta: Any? = null, usePost: Boolean = false, - ttl: Int? = null, ) = publish( channel = channel, message = message, @@ -306,7 +305,6 @@ class PubNubCore internal constructor( shouldStore = false, usePost = usePost, replicate = false, - ttl = ttl, ) /** diff --git a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/managers/MapperManager.kt b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/managers/MapperManager.kt index 0b917e2fb..69e2b9538 100644 --- a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/managers/MapperManager.kt +++ b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/managers/MapperManager.kt @@ -12,16 +12,20 @@ import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.google.gson.ToNumberPolicy import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException +import com.pubnub.api.utils.PatchValue import org.json.JSONArray import org.json.JSONException import org.json.JSONObject import retrofit2.Converter import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.ParameterizedType import java.lang.reflect.Type class MapperManager { @@ -42,7 +46,7 @@ class MapperManager { } } - override fun read(_in: JsonReader): Boolean? { + override fun read(_in: JsonReader): Boolean { val peek: JsonToken = _in.peek() return when (peek) { JsonToken.BOOLEAN -> _in.nextBoolean() @@ -53,6 +57,58 @@ class MapperManager { } } + val patchValueTypeFactory = object : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType != PatchValue::class.java) { + return null + } + val factory = this + return object : TypeAdapter() { + override fun write(out: JsonWriter, patchValue: T) { + val writeNulls = out.serializeNulls + try { + if (patchValue == null) { + // value is PatchValue.none(), skip it (serializeNulls is false) + out.nullValue() + } else { + patchValue as PatchValue + if (patchValue.value == null) { + // value is PatchValue.of(null), write it out to JSON: + out.serializeNulls = true + out.nullValue() + } else { + // value is PatchValue.of(something), write it out using the right adapter: + val delegate = gson.getDelegateAdapter( + factory, + TypeToken.get(patchValue.value!!::class.java) + ) as TypeAdapter + delegate.write(out, patchValue.value) + } + } + } finally { + out.serializeNulls = writeNulls + } + } + + override fun read(reader: JsonReader): T { + val token = reader.peek() + if (token == JsonToken.NULL) { + reader.nextNull() + @Suppress("UNCHECKED_CAST") + return PatchValue.of(null) as T + } else { + val delegate = gson.getDelegateAdapter( + factory, + TypeToken.get((type.type as ParameterizedType).actualTypeArguments.first()) + ) + @Suppress("UNCHECKED_CAST") + return PatchValue.of(delegate.read(reader)) as T + } + } + } + } + } + objectMapper = GsonBuilder() .registerTypeAdapter(Boolean::class.javaObjectType, booleanAsIntAdapter) @@ -60,6 +116,7 @@ class MapperManager { .registerTypeAdapter(Boolean::class.java, booleanAsIntAdapter) .registerTypeAdapter(JSONObject::class.java, JSONObjectAdapter()) .registerTypeAdapter(JSONArray::class.java, JSONArrayAdapter()) + .registerTypeAdapterFactory(patchValueTypeFactory) .disableHtmlEscaping() .setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) .create() @@ -144,7 +201,7 @@ class MapperManager { clazz: Class, ): T { return try { - this.objectMapper.fromJson(input, clazz) + this.objectMapper.fromJson(input, clazz) } catch (e: JsonParseException) { throw PubNubException( pubnubError = PubNubError.PARSING_ERROR, diff --git a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/membership/PNChannelMembership.kt b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/membership/PNChannelMembership.kt index 8178158fb..fd72a11d5 100644 --- a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/membership/PNChannelMembership.kt +++ b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/consumer/objects/membership/PNChannelMembership.kt @@ -3,7 +3,7 @@ package com.pubnub.internal.models.consumer.objects.membership import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata data class PNChannelMembership( - val channel: PNChannelMetadata?, + val channel: PNChannelMetadata, val custom: Map?, val updated: String, val eTag: String, diff --git a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/server/objects_api/EntityEnvelope.kt b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/server/objects_api/EntityEnvelope.kt index f23192acc..e5078f99f 100644 --- a/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/server/objects_api/EntityEnvelope.kt +++ b/pubnub-core/pubnub-core-impl/src/main/kotlin/com/pubnub/internal/models/server/objects_api/EntityEnvelope.kt @@ -2,5 +2,5 @@ package com.pubnub.internal.models.server.objects_api data class EntityEnvelope( val status: Int, - val data: T? = null, + val data: T, ) diff --git a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/channelmetadata/step/WhenSteps.kt b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/channelmetadata/step/WhenSteps.kt index b93f324ac..de57d3909 100644 --- a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/channelmetadata/step/WhenSteps.kt +++ b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/channelmetadata/step/WhenSteps.kt @@ -21,11 +21,11 @@ class WhenSteps( val channelMetadata = channelMetadataState.channelMetadata!! world.pubnub.pubNubCore.setChannelMetadata( channel = channelMetadata.id, - name = channelMetadata.name, - description = channelMetadata.description, - custom = channelMetadata.custom, - type = channelMetadata.type, - status = channelMetadata.status, + name = channelMetadata.name?.value, + description = channelMetadata.description?.value, + custom = channelMetadata.custom?.value, + type = channelMetadata.type?.value, + status = channelMetadata.status?.value, ).sync().let { channelMetadataState.channelMetadata = it.data world.responseStatus = it.status diff --git a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/uuidmetadata/step/WhenSteps.kt b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/uuidmetadata/step/WhenSteps.kt index 50a9d47a4..8e045606b 100644 --- a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/uuidmetadata/step/WhenSteps.kt +++ b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/contract/uuidmetadata/step/WhenSteps.kt @@ -30,13 +30,13 @@ class WhenSteps( world.pubnub.pubNubCore.setUUIDMetadata( uuid = uuidMetadata.id, custom = uuidMetadata.custom, - externalId = uuidMetadata.externalId, - profileUrl = uuidMetadata.profileUrl, - email = uuidMetadata.email, - name = uuidMetadata.name, - status = uuidMetadata.status, - type = uuidMetadata.type, - ).sync()?.let { + externalId = uuidMetadata.externalId?.value, + profileUrl = uuidMetadata.profileUrl?.value, + email = uuidMetadata.email?.value, + name = uuidMetadata.name?.value, + status = uuidMetadata.status?.value, + type = uuidMetadata.type?.value, + ).sync().let { uuidMetadataState.uuidMetadata = it.data world.responseStatus = it.status } diff --git a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/internal/managers/MapperManagerTest.kt b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/internal/managers/MapperManagerTest.kt index 251d61854..ce31bb4aa 100644 --- a/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/internal/managers/MapperManagerTest.kt +++ b/pubnub-core/pubnub-core-impl/src/test/kotlin/com/pubnub/internal/managers/MapperManagerTest.kt @@ -1,7 +1,10 @@ package com.pubnub.internal.managers import com.pubnub.api.PubNubException +import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test internal class MapperManagerTest { @@ -94,6 +97,29 @@ internal class MapperManagerTest { assertEquals(map["f"], decodedMap["f"]) assertEquals(map["f"].toString().toLong(), decodedMap["f"].tryLong()) } + + @Test + fun fromJson_optionalsAndNulls() { + val mapperManager = MapperManager() + val input = """ + { "id" : "myId", "name": null, "description": "myDescription", "eTag": "myEtag", "custom": { "a" : "b" } } + """.trimIndent() + + val output: PNChannelMetadata = mapperManager.fromJson(input, PNChannelMetadata::class.java) + + assertEquals("myId", output.id) + assertNotNull(output.name) + assertNull(output.name?.value) + + assertEquals("myDescription", output.description?.value) + + assertNull(output.updated) + assertNull(output.status) + + assertEquals(mapOf("a" to "b"), output.custom?.value) + + assertEquals("myEtag", output.eTag?.value) + } } private fun Any?.tryLong(): Long? { diff --git a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java index 5b4cfb67f..84d7f77be 100644 --- a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java +++ b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java @@ -6,7 +6,7 @@ import lombok.Setter; import lombok.ToString; import lombok.experimental.Accessors; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -39,26 +39,25 @@ public PNChannelMetadata setCustom(Object custom) { return this; } - @Nullable - public static PNChannelMetadata from(@Nullable com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata data) { - if (data == null) { - return null; - } + @NotNull + public static PNChannelMetadata from(@NotNull com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata data) { PNChannelMetadata newData = new PNChannelMetadata( - data.getId(), data.getName(), data.getDescription() + data.getId(), + data.getName() != null ? data.getName().getValue() : null, + data.getDescription() != null ? data.getDescription().getValue() : null ); - newData.setETag(data.getETag()); - newData.setType(data.getType()); - newData.setStatus(data.getStatus()); - newData.setCustom(data.getCustom()); - newData.setUpdated(data.getUpdated()); + newData.setETag(data.getETag() != null ? data.getETag().getValue() : null); + newData.setType(data.getType() != null ? data.getType().getValue() : null); + newData.setStatus(data.getStatus() != null ? data.getStatus().getValue() : null); + newData.setCustom(data.getCustom() != null ? data.getCustom().getValue() : null); + newData.setUpdated(data.getUpdated() != null ? data.getUpdated().getValue() : null); return newData; } public static List from(Collection data) { List channels = new ArrayList<>(data.size()); for (com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata datum : data) { - channels.add(PNChannelMetadata.from(datum)); + channels.add(from(datum)); } return channels; } diff --git a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java index 57220cc6b..409eab008 100644 --- a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java +++ b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java @@ -51,16 +51,16 @@ public static PNUUIDMetadata from(com.pubnub.api.models.consumer.objects.uuid.PN if (data == null) { return null; } - PNUUIDMetadata newData = new PNUUIDMetadata(data.getId(), data.getName()) - .setProfileUrl(data.getProfileUrl()) - .setEmail(data.getEmail()) - .setExternalId(data.getExternalId()) - .setStatus(data.getStatus()) - .setType(data.getType()) - .setCustom(data.getCustom()); + PNUUIDMetadata newData = new PNUUIDMetadata(data.getId(), data.getName() != null ? data.getName().getValue() : null) + .setProfileUrl(data.getProfileUrl() != null ? data.getProfileUrl().getValue() : null) + .setEmail(data.getEmail() != null ? data.getEmail().getValue() : null) + .setExternalId(data.getExternalId() != null ? data.getExternalId().getValue() : null) + .setStatus(data.getStatus() != null ? data.getStatus().getValue() : null) + .setType(data.getType() != null ? data.getType().getValue() : null) + .setCustom(data.getCustom() != null ? data.getCustom().getValue() : null); - newData.setETag(data.getETag()); - newData.setUpdated(data.getUpdated()); + newData.setETag(data.getETag() != null ? data.getETag().getValue() : null); + newData.setUpdated(data.getUpdated() != null ? data.getUpdated().getValue() : null); return newData; } } diff --git a/pubnub-gson/pubnub-gson-impl/src/test/kotlin/com/pubnub/internal/v2/callbacks/DelegatingEventListenerTest.kt b/pubnub-gson/pubnub-gson-impl/src/test/kotlin/com/pubnub/internal/v2/callbacks/DelegatingEventListenerTest.kt index 4d63fafa0..49563754f 100644 --- a/pubnub-gson/pubnub-gson-impl/src/test/kotlin/com/pubnub/internal/v2/callbacks/DelegatingEventListenerTest.kt +++ b/pubnub-gson/pubnub-gson-impl/src/test/kotlin/com/pubnub/internal/v2/callbacks/DelegatingEventListenerTest.kt @@ -4,12 +4,12 @@ import com.google.gson.JsonPrimitive import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.UserId -import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata import com.pubnub.api.models.consumer.objects.uuid.PNUUIDMetadata import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult import com.pubnub.api.models.consumer.pubsub.BasePubSubResult +import com.pubnub.api.utils.PatchValue import com.pubnub.api.v2.callbacks.EventListener import com.pubnub.internal.models.consumer.pubsub.objects.PNDeleteChannelMetadataEventMessage import com.pubnub.internal.models.consumer.pubsub.objects.PNDeleteMembershipEvent @@ -82,7 +82,13 @@ internal class DelegatingEventListenerTest { pn, PNObjectEventResult( BasePubSubResult("a", "b", 0L, null, null), - PNSetUUIDMetadataEventMessage("a", "b", "c", "d", PNUUIDMetadata("a", "b", null, "c", "d", null, null, null, null, null)), + PNSetUUIDMetadataEventMessage( + "a", + "b", + "c", + "d", + PNUUIDMetadata("a", PatchValue.of("b"), null, PatchValue.of("c"), PatchValue.of("d"), null, null, null, null, null) + ), ), ) delegating.objects( @@ -94,7 +100,16 @@ internal class DelegatingEventListenerTest { "b", "c", "d", - PNChannelMetadata("a", "b", null, mapOf("c" to "c"), "d", null, null, null) + com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata( + "a", + PatchValue.of("b"), + null, + PatchValue.of(mapOf("c" to "c")), + PatchValue.of("d"), + null, + null, + null + ) ), ), ) @@ -129,7 +144,18 @@ internal class DelegatingEventListenerTest { @Test fun getSetUuidMetadataResult() { - val metadata = PNUUIDMetadata(id, name, externalId, profileUrl, email, custom, updated, eTag, type, status) + val metadata = PNUUIDMetadata( + id, + PatchValue.of(name), + PatchValue.of(externalId), + PatchValue.of(profileUrl), + PatchValue.of(email), + PatchValue.of(custom), + PatchValue.of(updated), + PatchValue.of(eTag), + PatchValue.of(type), + PatchValue.of(status) + ) val message = PNSetUUIDMetadataEventMessage(source, version, event, type, metadata) val objectEvent = PNObjectEventResult(BasePubSubResult(channel, subscription, timetoken, userMetadata, publisher), message) @@ -183,7 +209,17 @@ internal class DelegatingEventListenerTest { @Test fun getSetChannelMetadataResult() { - val metadata = PNChannelMetadata(id, name, description, custom, updated, eTag, type, status) + val metadata = + com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata( + id, + PatchValue.of(name), + PatchValue.of(description), + PatchValue.of(custom), + PatchValue.of(updated), + PatchValue.of(eTag), + PatchValue.of(type), + PatchValue.of(status) + ) val message = PNSetChannelMetadataEventMessage(source, version, event, type, metadata) val objectEvent = PNObjectEventResult(BasePubSubResult(channel, subscription, timetoken, userMetadata, publisher), message) diff --git a/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml b/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml index 7d091a9a8..981420778 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml +++ b/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml @@ -1,81 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt index d626c11de..f410310e8 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt @@ -100,7 +100,6 @@ expect interface PubNub { message: Any, meta: Any? = null, usePost: Boolean = false, - ttl: Int? = null, ): Publish fun signal( diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembership.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembership.kt index e857e892c..98284a856 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembership.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/PNChannelMembership.kt @@ -4,7 +4,7 @@ import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata import com.pubnub.kmp.CustomObject data class PNChannelMembership( - val channel: PNChannelMetadata?, + val channel: PNChannelMetadata, val custom: Map?, val updated: String, val eTag: String, diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/v2/entities/Channel.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/v2/entities/Channel.kt index 2ed22defb..5b5ad4df1 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/v2/entities/Channel.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/v2/entities/Channel.kt @@ -110,9 +110,9 @@ interface Channel : BaseChannel { */ fun fire( message: Any, - meta: Any?, - usePost: Boolean, - ttl: Int? + meta: Any? = null, + usePost: Boolean = false, + ttl: Int? = null ): Publish /** diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataTest.kt new file mode 100644 index 000000000..0c454aeea --- /dev/null +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/channel/PNChannelMetadataTest.kt @@ -0,0 +1,43 @@ +package com.pubnub.api.models.consumer.objects.channel + +import com.pubnub.api.utils.PatchValue +import com.pubnub.test.randomString +import kotlin.test.Test +import kotlin.test.assertEquals + +class PNChannelMetadataTest { + @Test + fun plus() { + val originalMetadata = PNChannelMetadata( + randomString(), + name = PatchValue.of(randomString()), + description = PatchValue.of(randomString()), + custom = PatchValue.of(mapOf(randomString() to randomString())), + updated = PatchValue.of(randomString()), + eTag = PatchValue.of(randomString()), + type = PatchValue.of(randomString()), + status = PatchValue.of(randomString()) + ) + val updateMetadata = PNChannelMetadata( + originalMetadata.id, + name = PatchValue.of(randomString()), + description = PatchValue.of(randomString()), + custom = PatchValue.of(null), + ) + + val resultingMetadata = originalMetadata + updateMetadata + + val expectedMetadata = PNChannelMetadata( + originalMetadata.id, + name = updateMetadata.name, + description = updateMetadata.description, + custom = updateMetadata.custom, + updated = originalMetadata.updated, + type = originalMetadata.type, + status = originalMetadata.status, + eTag = originalMetadata.eTag, + ) + + assertEquals(expectedMetadata, resultingMetadata) + } +} diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadataTest.kt new file mode 100644 index 000000000..818501625 --- /dev/null +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/api/models/consumer/objects/uuid/PNUUIDMetadataTest.kt @@ -0,0 +1,47 @@ +package com.pubnub.api.models.consumer.objects.uuid + +import com.pubnub.api.utils.PatchValue +import com.pubnub.test.randomString +import kotlin.test.Test +import kotlin.test.assertEquals + +class PNUUIDMetadataTest { + @Test + fun plus() { + val originalMetadata = PNUUIDMetadata( + randomString(), + name = PatchValue.of(randomString()), + externalId = PatchValue.of(randomString()), + profileUrl = PatchValue.of(randomString()), + email = PatchValue.of(randomString()), + custom = PatchValue.of(mapOf(randomString() to randomString())), + updated = PatchValue.of(randomString()), + eTag = PatchValue.of(randomString()), + type = PatchValue.of(randomString()), + status = PatchValue.of(randomString()) + ) + val updateMetadata = PNUUIDMetadata( + originalMetadata.id, + name = PatchValue.of(randomString()), + updated = PatchValue.of(randomString()), + custom = PatchValue.of(null), + ) + + val resultingMetadata = originalMetadata + updateMetadata + + val expectedMetadata = PNUUIDMetadata( + originalMetadata.id, + updateMetadata.name, + originalMetadata.externalId, + originalMetadata.profileUrl, + originalMetadata.email, + updateMetadata.custom, + updateMetadata.updated, + originalMetadata.eTag, + originalMetadata.type, + originalMetadata.status + ) + + assertEquals(expectedMetadata, resultingMetadata) + } +} diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt index 9d7d405fe..49cc63043 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt @@ -44,13 +44,12 @@ class ChannelMetadataTest : BaseIntegrationTest() { // then val pnuuidMetadata = result.data - requireNotNull(pnuuidMetadata) assertEquals(channel, pnuuidMetadata.id) - assertEquals(name, pnuuidMetadata.name) - assertEquals(status, pnuuidMetadata.status) - assertEquals(customData, pnuuidMetadata.custom) - assertEquals(type, pnuuidMetadata.type) - assertEquals(description, pnuuidMetadata.description) + assertEquals(name, pnuuidMetadata.name?.value) + assertEquals(status, pnuuidMetadata.status?.value) + assertEquals(customData, pnuuidMetadata.custom?.value) + assertEquals(type, pnuuidMetadata.type?.value) + assertEquals(description, pnuuidMetadata.description?.value) } @Test @@ -63,9 +62,9 @@ class ChannelMetadataTest : BaseIntegrationTest() { pubnub.setChannelMetadata( channel, name = name, - status = status, + status = null, custom = custom, - includeCustom = includeCustom, + includeCustom = false, type = type, description = description ).await() @@ -75,11 +74,11 @@ class ChannelMetadataTest : BaseIntegrationTest() { val message = result.extractedMessage message as PNSetChannelMetadataEventMessage assertEquals(channel, message.data.id) - assertEquals(name, message.data.name) - assertEquals(description, message.data.description) - assertEquals(status, message.data.status) - assertEquals(customData, message.data.custom) - assertEquals(type, message.data.type) + assertEquals(name, message.data.name?.value) + assertEquals(description, message.data.description?.value) + assertEquals(null, message.data.status?.value) + assertEquals(customData, message.data.custom?.value) + assertEquals(type, message.data.type?.value) } } @@ -146,7 +145,6 @@ class ChannelMetadataTest : BaseIntegrationTest() { custom = custom, includeCustom = includeCustom, type = type, - description = description ).await() } @@ -177,11 +175,11 @@ class ChannelMetadataTest : BaseIntegrationTest() { repeat(6) { index -> val pnChannelMetadata = allChannels.firstOrNull { it.id == channel + index } assertNotNull(pnChannelMetadata) - assertEquals(name, pnChannelMetadata.name) - assertEquals(description, pnChannelMetadata.description) - assertEquals(status, pnChannelMetadata.status) - assertEquals(customData, pnChannelMetadata.custom) - assertEquals(type, pnChannelMetadata.type) + assertEquals(name, pnChannelMetadata.name?.value) + assertEquals(null, pnChannelMetadata.description!!.value) + assertEquals(status, pnChannelMetadata.status?.value) + assertEquals(customData, pnChannelMetadata.custom?.value) + assertEquals(type, pnChannelMetadata.type?.value) } } } diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt index 6b8167fd3..7ec496231 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt @@ -17,6 +17,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.time.Duration.Companion.seconds class UserMetadataTest : BaseIntegrationTest() { @@ -50,13 +51,13 @@ class UserMetadataTest : BaseIntegrationTest() { val pnuuidMetadata = result.data requireNotNull(pnuuidMetadata) assertEquals(uuid, pnuuidMetadata.id) - assertEquals(name, pnuuidMetadata.name) - assertEquals(externalId, pnuuidMetadata.externalId) - assertEquals(profileUrl, pnuuidMetadata.profileUrl) - assertEquals(email, pnuuidMetadata.email) - assertEquals(status, pnuuidMetadata.status) - assertEquals(customData, pnuuidMetadata.custom) - assertEquals(type, pnuuidMetadata.type) + assertEquals(name, pnuuidMetadata.name?.value) + assertEquals(externalId, pnuuidMetadata.externalId?.value) + assertEquals(profileUrl, pnuuidMetadata.profileUrl?.value) + assertEquals(email, pnuuidMetadata.email?.value) + assertEquals(status, pnuuidMetadata.status?.value) + assertEquals(customData, pnuuidMetadata.custom?.value) + assertEquals(type, pnuuidMetadata.type?.value) } @Test @@ -68,7 +69,6 @@ class UserMetadataTest : BaseIntegrationTest() { // when pubnub.setUUIDMetadata( uuid, - name = name, externalId = externalId, profileUrl = profileUrl, email = email, @@ -83,13 +83,13 @@ class UserMetadataTest : BaseIntegrationTest() { val message = result.extractedMessage message as PNSetUUIDMetadataEventMessage assertEquals(uuid, message.data.id) - assertEquals(name, message.data.name) - assertEquals(externalId, message.data.externalId) - assertEquals(profileUrl, message.data.profileUrl) - assertEquals(email, message.data.email) - assertEquals(status, message.data.status) - assertEquals(customData, message.data.custom) - assertEquals(type, message.data.type) + assertNull(message.data.name) + assertEquals(externalId, message.data.externalId?.value) + assertEquals(profileUrl, message.data.profileUrl?.value) + assertEquals(email, message.data.email?.value) + assertEquals(status, message.data.status?.value) + assertEquals(customData, message.data.custom?.value) + assertEquals(type, message.data.type?.value) } } @@ -166,13 +166,13 @@ class UserMetadataTest : BaseIntegrationTest() { val pnuuidMetadata = result.data requireNotNull(pnuuidMetadata) assertEquals(uuid, pnuuidMetadata.id) - assertEquals(name, pnuuidMetadata.name) - assertEquals(externalId, pnuuidMetadata.externalId) - assertEquals(profileUrl, pnuuidMetadata.profileUrl) - assertEquals(email, pnuuidMetadata.email) - assertEquals(status, pnuuidMetadata.status) - assertEquals(customData, pnuuidMetadata.custom) - assertEquals(type, pnuuidMetadata.type) + assertEquals(name, pnuuidMetadata.name?.value) + assertEquals(externalId, pnuuidMetadata.externalId?.value) + assertEquals(profileUrl, pnuuidMetadata.profileUrl?.value) + assertEquals(email, pnuuidMetadata.email?.value) + assertEquals(status, pnuuidMetadata.status?.value) + assertEquals(customData, pnuuidMetadata.custom?.value) + assertEquals(type, pnuuidMetadata.type?.value) } @Test @@ -219,13 +219,13 @@ class UserMetadataTest : BaseIntegrationTest() { repeat(6) { val pnuuidMetadata = allUsers.firstOrNull { user -> user.id == uuid + it } assertNotNull(pnuuidMetadata) - assertEquals(name, pnuuidMetadata.name) - assertEquals(externalId, pnuuidMetadata.externalId) - assertEquals(profileUrl, pnuuidMetadata.profileUrl) - assertEquals(email, pnuuidMetadata.email) - assertEquals(status, pnuuidMetadata.status) - assertEquals(customData, pnuuidMetadata.custom) - assertEquals(type, pnuuidMetadata.type) + assertEquals(name, pnuuidMetadata.name?.value) + assertEquals(externalId, pnuuidMetadata.externalId?.value) + assertEquals(profileUrl, pnuuidMetadata.profileUrl?.value) + assertEquals(email, pnuuidMetadata.email?.value) + assertEquals(status, pnuuidMetadata.status?.value) + assertEquals(customData, pnuuidMetadata.custom?.value) + assertEquals(type, pnuuidMetadata.type?.value) } } } diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt b/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt index d4ff9b6c7..e9a4336bc 100644 --- a/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt +++ b/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt @@ -218,20 +218,45 @@ actual interface PubNub : * @param meta Metadata object which can be used with the filtering ability. * If not specified, then the history configuration of the key is used. * @param usePost Use HTTP POST to publish. Default is `false` - * @param ttl Set a per message time to live in storage. - * - If `shouldStore = true`, and `ttl = 0`, the message is stored - * with no expiry time. - * - If `shouldStore = true` and `ttl = X` (`X` is an Integer value), - * the message is stored with an expiry time of `X` hours. - * - If `shouldStore = false`, the `ttl` parameter is ignored. - * - If ttl isn't specified, then expiration of the message defaults - * back to the expiry value for the key. */ actual fun fire( channel: String, message: Any, meta: Any?, usePost: Boolean, + ): Publish + + /** + * Send a message to PubNub Functions Event Handlers. + * + * These messages will go directly to any Event Handlers registered on the channel that you fire to + * and will trigger their execution. The content of the fired request will be available for processing + * within the Event Handler. + * + * The message sent via `fire()` isn't replicated, and so won't be received by any subscribers to the channel. + * The message is also not stored in history. + * + * + * @param message The payload. + * **Warning:** It is important to note that you should not serialize JSON + * when sending signals/messages via PubNub. + * Why? Because the serialization is done for you automatically. + * Instead just pass the full object as the message payload. + * PubNub takes care of everything for you. + * @param channel Destination of the message. + * @param meta Metadata object which can be used with the filtering ability. + * If not specified, then the history configuration of the key is used. + * @param usePost Use HTTP POST to publish. Default is `false` + */ + @Deprecated( + "`fire()` never used the `ttl` parameter, please use the version without `ttl`.", + replaceWith = ReplaceWith("fire(channel, message, meta, usePost)") + ) + fun fire( + channel: String, + message: Any, + meta: Any?, + usePost: Boolean, ttl: Int?, ): Publish diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt index 792075ff2..8439ce8be 100644 --- a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt +++ b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt @@ -176,7 +176,7 @@ class HistoryIntegrationTest : BaseIntegrationTest() { expectedActionValue to listOf( PNFetchMessageItem.Action( - actionTimetoken = actionResult.actionTimetoken.toString(), + actionTimetoken = actionResult.actionTimetoken!!, uuid = pubnub.configuration.userId.value, ), ), diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/ObjectsIntegrationTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/ObjectsIntegrationTest.kt index 2030718e8..b0867fb02 100644 --- a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/ObjectsIntegrationTest.kt +++ b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/ObjectsIntegrationTest.kt @@ -65,8 +65,8 @@ class ObjectsIntegrationTest : BaseIntegrationTest() { type = type, ).sync() - assertEquals(status, setResult.data?.status) - assertEquals(type, setResult.data?.type) + assertEquals(status, setResult.data?.status?.value) + assertEquals(type, setResult.data?.type?.value) val getAllResult = pubnub.getAllUUIDMetadata(filter = "id == \"$testUuid\"").sync() val getSingleResult = pubnub.getUUIDMetadata(uuid = testUuid).sync() @@ -267,12 +267,12 @@ class ObjectsIntegrationTest : BaseIntegrationTest() { custom = expectedCustom, includeCustom = true, ).sync().apply { - assertEquals(this.data?.id, expectedUUID) - assertEquals(this.data?.name, expectedName) - assertEquals(this.data?.email, expectedEmail) - assertEquals(this.data?.externalId, expectedExternalId) - assertEquals(this.data?.profileUrl, expectedProfileUrl) - assertEquals(this.data?.custom, expectedCustom) + assertEquals(expectedUUID, this.data?.id) + assertEquals(expectedName, this.data?.name?.value) + assertEquals(expectedEmail, this.data?.email?.value) + assertEquals(expectedExternalId, this.data?.externalId?.value) + assertEquals(expectedProfileUrl, this.data?.profileUrl?.value) + assertEquals(expectedCustom, this.data?.custom?.value) } } diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt index 05ce4e890..9203fbad7 100644 --- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt +++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt @@ -283,24 +283,29 @@ class PubNubImpl( ) } - override fun fire( - channel: String, - message: Any, - meta: Any?, - usePost: Boolean, - ttl: Int?, - ): Publish { + override fun fire(channel: String, message: Any, meta: Any?, usePost: Boolean): Publish { return com.pubnub.internal.endpoints.pubsub.PublishImpl( pubNubCore.fire( channel, message, meta, usePost, - ttl, ), ) } + @Deprecated( + "`fire()` never used the `ttl` parameter, please use the version without `ttl`.", + replaceWith = ReplaceWith("fire(channel, message, meta, usePost)") + ) + override fun fire( + channel: String, + message: Any, + meta: Any?, + usePost: Boolean, + ttl: Int?, + ): Publish = fire(channel, message, meta, usePost) + override fun signal( channel: String, message: Any, diff --git a/pubnub-kotlin/pubnub-kotlin-test/config/ktlint/baseline.xml b/pubnub-kotlin/pubnub-kotlin-test/config/ktlint/baseline.xml index 73faa4cf8..981420778 100644 --- a/pubnub-kotlin/pubnub-kotlin-test/config/ktlint/baseline.xml +++ b/pubnub-kotlin/pubnub-kotlin-test/config/ktlint/baseline.xml @@ -1,10 +1,3 @@ - - - - - - -