Skip to content

Commit

Permalink
Etag support for users and channels
Browse files Browse the repository at this point in the history
A new optional parameter `ifMatchesEtag` is added to `setUUIDMetadata` and `setChannelMetadata`.
When provided, the server checks the argument value with the ETag on the server and if they don't match a HTTP 412 error is returned.
  • Loading branch information
wkal-pubnub committed Jan 17, 2025
1 parent 11f1678 commit 5f6de23
Show file tree
Hide file tree
Showing 18 changed files with 175 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.pubnub.api.java.endpoints.BuilderSteps;
import com.pubnub.api.java.endpoints.Endpoint;
import com.pubnub.api.java.models.consumer.objects_api.channel.PNSetChannelMetadataResult;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

Expand All @@ -19,6 +20,8 @@ public interface SetChannelMetadata extends Endpoint<PNSetChannelMetadataResult>

SetChannelMetadata includeCustom(boolean includeCustom);

SetChannelMetadata ifMatchesEtag(@Nullable String ifMatchesEtag);

interface Builder extends BuilderSteps.ChannelStep<SetChannelMetadata> {
@Override
SetChannelMetadata channel(String channel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.pubnub.api.java.endpoints.Endpoint;
import com.pubnub.api.java.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

Expand All @@ -23,4 +24,6 @@ public interface SetUUIDMetadata extends Endpoint<PNSetUUIDMetadataResult> {
SetUUIDMetadata type(String type);

SetUUIDMetadata status(String status);

SetUUIDMetadata ifMatchesEtag(@Nullable String etag);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import lombok.Setter;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -46,7 +47,8 @@ protected Endpoint<PNChannelMetadataResult> createRemoteAction() {
custom,
includeCustom,
type,
status
status,
ifMatchesEtag
);
}

Expand All @@ -69,6 +71,10 @@ protected Endpoint<PNChannelMetadataResult> createRemoteAction() {
@Setter
private boolean includeCustom;

@Setter
@Nullable
private String ifMatchesEtag;

@Override
public SetChannelMetadata custom(Map<String, Object> custom) {
final HashMap<String, Object> customHashMap = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import lombok.Setter;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -37,6 +38,10 @@ public class SetUUIDMetadataImpl extends DelegatingEndpoint<PNUUIDMetadataResult
@Setter
private String status;

@Setter
@Nullable
private String ifMatchesEtag;

public SetUUIDMetadataImpl(final PubNub pubnub) {
super(pubnub);
}
Expand All @@ -63,7 +68,8 @@ protected Endpoint<PNUUIDMetadataResult> createRemoteAction() {
custom,
includeCustom,
type,
status
status,
ifMatchesEtag
);
}

Expand Down
34 changes: 14 additions & 20 deletions pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
<file name="src/commonMain/kotlin/com/pubnub/api/models/consumer/objects/membership/MembershipInclude.kt">
<error line="20" column="5" source="standard:function-naming" />
</file>
<file name="src/commonTest/kotlin/com/pubnub/test/integration/MembersTest.kt">
<error line="18" column="1" source="standard:blank-line-before-declaration" />
</file>
<file name="src/jsMain/kotlin/Pubnub.d.kt">
<error line="156" column="40" source="standard:comment-wrapping" />
<error line="411" column="15" source="standard:class-naming" />
Expand All @@ -22,19 +19,17 @@
<error line="849" column="13" source="standard:property-naming" />
<error line="1034" column="15" source="standard:class-naming" />
<error line="1040" column="15" source="standard:class-naming" />
<error line="1161" column="13" source="standard:property-naming" />
<error line="1169" column="13" source="standard:property-naming" />
<error line="1163" column="13" source="standard:property-naming" />
<error line="1171" column="13" source="standard:property-naming" />
<error line="1234" column="39" source="standard:comment-wrapping" />
<error line="1319" column="15" source="standard:class-naming" />
<error line="1347" column="42" source="standard:comment-wrapping" />
<error line="1397" column="37" source="standard:comment-wrapping" />
<error line="1418" column="13" source="standard:property-naming" />
<error line="1419" column="13" source="standard:property-naming" />
<error line="1423" column="13" source="standard:property-naming" />
<error line="1424" column="13" source="standard:property-naming" />
<error line="1443" column="9" source="standard:property-naming" />
<error line="1444" column="9" source="standard:property-naming" />
<error line="1173" column="13" source="standard:property-naming" />
<error line="1236" column="39" source="standard:comment-wrapping" />
<error line="1321" column="15" source="standard:class-naming" />
<error line="1349" column="42" source="standard:comment-wrapping" />
<error line="1399" column="37" source="standard:comment-wrapping" />
<error line="1420" column="13" source="standard:property-naming" />
<error line="1421" column="13" source="standard:property-naming" />
<error line="1425" column="13" source="standard:property-naming" />
<error line="1426" column="13" source="standard:property-naming" />
<error line="1445" column="9" source="standard:property-naming" />
<error line="1446" column="9" source="standard:property-naming" />
<error line="1447" column="9" source="standard:property-naming" />
Expand All @@ -46,8 +41,8 @@
<error line="1453" column="9" source="standard:property-naming" />
<error line="1454" column="9" source="standard:property-naming" />
<error line="1455" column="9" source="standard:property-naming" />
<error line="1459" column="9" source="standard:property-naming" />
<error line="1460" column="9" source="standard:property-naming" />
<error line="1456" column="9" source="standard:property-naming" />
<error line="1457" column="9" source="standard:property-naming" />
<error line="1461" column="9" source="standard:property-naming" />
<error line="1462" column="9" source="standard:property-naming" />
<error line="1463" column="9" source="standard:property-naming" />
Expand Down Expand Up @@ -82,8 +77,7 @@
<error line="1492" column="9" source="standard:property-naming" />
<error line="1493" column="9" source="standard:property-naming" />
<error line="1494" column="9" source="standard:property-naming" />
</file>
<file name="src/jsMain/kotlin/com/pubnub/api/PubNubImpl.kt">
<error line="1446" column="12" source="standard:no-multi-spaces" />
<error line="1495" column="9" source="standard:property-naming" />
<error line="1496" column="9" source="standard:property-naming" />
</file>
</baseline>
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
status: String?
status: String?,
ifMatchesEtag: String?,
): SetChannelMetadata {
return SetChannelMetadataImpl(
pubnub = pubNubObjC,
Expand All @@ -523,7 +524,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom = custom,
includeCustom = includeCustom,
type = type,
status = status
status = status,
ifMatchesEtag = ifMatchesEtag,
)
}

Expand Down Expand Up @@ -570,7 +572,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
status: String?
status: String?,
ifMatchesEtag: String?,
): SetUUIDMetadata {
return SetUUIDMetadataImpl(
pubnub = pubNubObjC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class SetChannelMetadataImpl(
private val custom: CustomObject?,
private val includeCustom: Boolean,
private val type: String?,
private val status: String?
private val status: String?,
private val ifMatchesEtag: String? = null,
) : SetChannelMetadata {
override fun async(callback: Consumer<Result<PNChannelMetadataResult>>) {
pubnub.setChannelMetadataWithMetadataId(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ expect interface PubNub {
includeCustom: Boolean = false,
type: String? = null,
status: String? = null,
ifMatchesEtag: String? = null,
): SetChannelMetadata

fun removeChannelMetadata(channel: String): RemoveChannelMetadata
Expand Down Expand Up @@ -296,6 +297,7 @@ expect interface PubNub {
includeCustom: Boolean = false,
type: String? = null,
status: String? = null,
ifMatchesEtag: String? = null,
): SetUUIDMetadata

fun removeUUIDMetadata(uuid: String? = null): RemoveUUIDMetadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,58 @@ class ChannelMetadataTest : BaseIntegrationTest() {
assertEquals(description, pnuuidMetadata.description?.value)
}

@Test
fun set_metadata_ifMatch_allows_change() = runTest {
// given
val result = pubnub.setChannelMetadata(
channel,
name = name,
status = status,
custom = custom,
includeCustom = includeCustom,
type = type,
description = description
).await()

val pnChannelMetadata = result.data

// when
val newData = pubnub.setChannelMetadata(
channel,
status = "someNewStatus",
ifMatchesEtag = pnChannelMetadata.eTag?.value
).await().data

// then
assertEquals("someNewStatus", newData.status?.value)
}

@Test
fun set_metadata_ifMatch_prohibits_change() = runTest {
// given
val result = pubnub.setChannelMetadata(
channel,
name = name,
status = status,
custom = custom,
includeCustom = includeCustom,
type = type,
description = description
).await()

val pnChannelMetadata = result.data

pubnub.setChannelMetadata(channel, name = "someNewName").await()

// when
val ex = assertFailsWith<PubNubException> {
pubnub.setChannelMetadata(channel, status = "someNewStatus", ifMatchesEtag = pnChannelMetadata.eTag?.value).await()
}

// then
assertEquals(HTTP_PRECONDITION_FAILED, ex.statusCode)
}

@Test
fun can_receive_set_metadata_event() = runTest {
pubnub.test(backgroundScope) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.time.Duration.Companion.seconds

internal const val HTTP_PRECONDITION_FAILED = 412

class UserMetadataTest : BaseIntegrationTest() {
private val uuid = "myUser" + randomString()
private val name = randomString()
Expand Down Expand Up @@ -60,6 +62,57 @@ class UserMetadataTest : BaseIntegrationTest() {
assertEquals(type, pnuuidMetadata.type?.value)
}

@Test
fun set_metadata_ifMatch_allows_change() = runTest {
// given
val result = pubnub.setUUIDMetadata(
uuid,
name = name,
externalId = externalId,
profileUrl = profileUrl,
email = email,
status = status,
custom = custom,
includeCustom = includeCustom,
type = type
).await()

val pnuuidMetadata = result.data

// when
val newData = pubnub.setUUIDMetadata(uuid, externalId = "someNewId", ifMatchesEtag = pnuuidMetadata.eTag?.value).await().data

// then
assertEquals("someNewId", newData.externalId?.value)
}

@Test
fun set_metadata_ifMatch_prohibits_change() = runTest {
// given
val result = pubnub.setUUIDMetadata(
uuid,
name = name,
externalId = externalId,
profileUrl = profileUrl,
email = email,
status = status,
custom = custom,
includeCustom = includeCustom,
type = type
).await()

val pnuuidMetadata = result.data
pubnub.setUUIDMetadata(uuid, name = "someNewName").await()

// when
val ex = assertFailsWith<PubNubException> {
pubnub.setUUIDMetadata(uuid, externalId = "someNewId", ifMatchesEtag = pnuuidMetadata.eTag?.value).await()
}

// then
assertEquals(HTTP_PRECONDITION_FAILED, ex.statusCode)
}

@Test
fun can_receive_set_metadata_event() = runTest {
pubnub.test(backgroundScope) {
Expand Down
2 changes: 2 additions & 0 deletions pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/Pubnub.d.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,7 @@ open external class PubNub(config: Any /* UUID | UserId */) {

var data: UUIDMetadata
var include: UuidIncludeCustom?
var ifMatchesEtag: String?
}

interface RemoveUUIDMetadataParameters {
Expand Down Expand Up @@ -1121,6 +1122,7 @@ open external class PubNub(config: Any /* UUID | UserId */) {
var channel: String
var data: ChannelMetadata
var include: UuidIncludeCustom?
var ifMatchesEtag: String?
}

interface RemoveChannelMetadataParameters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,8 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
status: String?
status: String?,
ifMatchesEtag: String?,
): SetChannelMetadata {
return SetChannelMetadataImpl(
jsPubNub,
Expand All @@ -576,6 +577,7 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
PatchValue.of(type),
PatchValue.of(custom),
)
this.ifMatchesEtag = ifMatchesEtag

this.include = createJsObject<PubNubJs.UuidIncludeCustom> {
this.customFields = includeCustom
Expand Down Expand Up @@ -637,7 +639,8 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
status: String?
status: String?,
ifMatchesEtag: String?,
): SetUUIDMetadata {
return SetUUIDMetadataImpl(
jsPubNub,
Expand All @@ -652,6 +655,7 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
PatchValue.of(custom),
)
this.uuid = uuid
this.ifMatchesEtag = ifMatchesEtag

include = createJsObject<PubNubJs.UuidIncludeCustom> {
this.customFields = includeCustom
Expand Down
Loading

0 comments on commit 5f6de23

Please sign in to comment.