Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,20 @@ internal interface MessageApi {
/**
* [REST documentation](https://getstream.io/chat/docs/rest/#messages-sendmessage)
*/
@POST("/channels/{type}/{id}/message")
@POST("/api/v2/chat/channels/{type}/{id}/message")
fun sendMessage(
@Path("type") channelType: String,
@Path("id") channelId: String,
@Body message: SendMessageRequest,
): RetrofitCall<SendMessageResponse>

@GET("/messages/{id}")
@GET("/api/v2/chat/messages/{id}")
fun getMessage(@Path("id") messageId: String): RetrofitCall<GetMessageResponse>

/**
* [REST documentation]()https://getstream.io/chat/docs/rest/#messages-updatemessage)
* [REST documentation](https://getstream.io/chat/docs/rest/#messages-updatemessage)
*/
@POST("/messages/{id}")
@POST("/api/v2/chat/messages/{id}")
fun updateMessage(
@Path("id") messageId: String,
@Body message: UpdateMessageRequest,
Expand All @@ -74,77 +74,77 @@ internal interface MessageApi {
/**
* [Rest documentation](https://getstream.io/chat/docs/rest/#messages-updatemessagepartial-request)
*/
@PUT("/messages/{id}")
@PUT("/api/v2/chat/messages/{id}")
fun partialUpdateMessage(
@Path("id") messageId: String,
@Body body: UpdateMessagePartialRequest,
): RetrofitCall<UpdateMessagePartialResponse>

@DELETE("/messages/{id}")
@DELETE("/api/v2/chat/messages/{id}")
fun deleteMessage(
@Path("id") messageId: String,
@Query("hard") hard: Boolean?,
@Query("delete_for_me") deleteForMe: Boolean?,
): RetrofitCall<DeleteMessageResponse>

@POST("/messages/{id}/action")
@POST("/api/v2/chat/messages/{id}/action")
fun sendAction(
@Path("id") messageId: String,
@Body request: MessageActionRequest,
): RetrofitCall<MessageActionResponse>

@POST("/messages/{id}/reaction")
@POST("/api/v2/chat/messages/{id}/reaction")
fun sendReaction(
@Path("id") messageId: String,
@Body request: SendReactionRequest,
): RetrofitCall<SendReactionResponse>

@DELETE("/messages/{id}/reaction/{type}")
@DELETE("/api/v2/chat/messages/{id}/reaction/{type}")
fun deleteReaction(
@Path("id") messageId: String,
@Path("type") reactionType: String,
): RetrofitCall<DeleteReactionResponse>

@GET("/messages/{id}/reactions")
@GET("/api/v2/chat/messages/{id}/reactions")
fun getReactions(
@Path("id") messageId: String,
@Query("offset") offset: Int,
@Query("limit") limit: Int,
): RetrofitCall<GetReactionsResponse>

@POST("/messages/{id}/reactions")
@POST("/api/v2/chat/messages/{id}/reactions")
fun queryReactions(
@Path("id") messageId: String,
@Body body: QueryReactionsRequest,
): RetrofitCall<QueryReactionsResponse>

@POST("/messages/{messageId}/translate")
@POST("/api/v2/chat/messages/{messageId}/translate")
fun translate(
@Path("messageId") messageId: String,
@Body request: TranslateMessageRequest,
): RetrofitCall<TranslateMessageResponse>

@GET("/messages/{parent_id}/replies")
@GET("/api/v2/chat/messages/{parent_id}/replies")
fun getReplies(
@Path("parent_id") messageId: String,
@Query("limit") limit: Int,
): RetrofitCall<GetRepliesResponse>

@GET("/messages/{parent_id}/replies?sort=[{\"field\":\"created_at\",\"direction\":1}]")
@GET("/api/v2/chat/messages/{parent_id}/replies?sort=[{\"field\":\"created_at\",\"direction\":1}]")
fun getNewerReplies(
@Path("parent_id") parentId: String,
@Query("limit") limit: Int,
@Query("id_gt") lastId: String?,
): RetrofitCall<GetRepliesResponse>

@GET("/messages/{parent_id}/replies")
@GET("/api/v2/chat/messages/{parent_id}/replies")
fun getRepliesMore(
@Path("parent_id") messageId: String,
@Query("limit") limit: Int,
@Query("id_lt") firstId: String,
): RetrofitCall<GetRepliesResponse>

@GET("/messages/{parent_id}/replies")
@GET("/api/v2/chat/messages/{parent_id}/replies")
fun getRepliesAround(
@Path("parent_id") parentId: String,
@Query("limit") limit: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.getstream.chat.android.client.api2.model.dto.DeviceDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamConnectedEventDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamUserDto
import io.getstream.chat.android.client.events.ConnectedEvent
import io.getstream.chat.android.client.parser2.adapters.GENERATED_MESSAGE_EXTRA_DATA_KEYS
import io.getstream.chat.android.client.parser2.adapters.internal.StreamDateFormatter
import io.getstream.chat.android.models.Attachment
import io.getstream.chat.android.models.ChatPreferences
Expand Down Expand Up @@ -135,6 +136,9 @@ internal class DtoMapping(

private fun MutableMap<String, Any>.takeString(key: String): String? = takeTyped(key) { it as? String }

private fun MutableMap<String, Any>.takeStrings(key: String): List<String>? =
takeTyped(key) { list -> (list as List<*>).map { it as String } }

private fun <T> MutableMap<String, Any>.takeList(key: String, adapter: JsonAdapter<T>): List<T>? =
takeTyped(key) { list -> (list as List<*>).map { adapter.fromJsonValue(it)!! } }

Expand Down Expand Up @@ -230,6 +234,12 @@ internal class DtoMapping(
messageTransformer.transform(this)
.run {
val upstreamType = if (type in supportedUpstreamMessageTypes) type else ""
// Responses also copy these declared fields into extraData (see DomainMapping.withDeclared), so a
// message sent back carries them. They go back to their typed fields, taking precedence as they did
// when v1 flattened extraData over the root. The response-only ones are dropped, since the backend
// would store them as custom data on either version.
val custom = extraData.toMutableMap()
custom.keys.removeAll(RESPONSE_ONLY_MESSAGE_KEYS)
MessageRequest(
id = id,
text = text,
Expand All @@ -238,18 +248,21 @@ internal class DtoMapping(
mentionedUsers = mentionedUsersIds,
mentionedHere = mentionedHere,
mentionedChannel = mentionedChannel,
mentionedGroupIds = mentionedGroups.map(UserGroup::id),
mentionedGroupIds = custom.takeStrings(MENTIONED_GROUP_IDS_KEY)
?: mentionedGroups.map(UserGroup::id),
mentionedRoles = mentionedRoles,
mml = custom.takeString(MML_KEY),
pollId = custom.takeString(POLL_ID_KEY),
parentId = parentId,
pinExpires = pinExpires,
pinned = pinned,
pinnedAt = pinnedAt,
quotedMessageId = replyMessageId,
showInChannel = showInChannel,
silent = silent,
restrictedVisibility = restrictedVisibility,
restrictedVisibility = custom.takeStrings(RESTRICTED_VISIBILITY_KEY) ?: restrictedVisibility,
sharedLocation = sharedLocation?.toSharedLocation(),
custom = extraData,
custom = custom,
)
}

Expand Down Expand Up @@ -360,3 +373,18 @@ internal class DtoMapping(
defaultPreference = defaultPreference?.value?.let(ChatPreferencesInput.DefaultPreference::fromString),
)
}

private const val MENTIONED_GROUP_IDS_KEY = "mentioned_group_ids"
private const val MML_KEY = "mml"
private const val POLL_ID_KEY = "poll_id"
private const val RESTRICTED_VISIBILITY_KEY = "restricted_visibility"

/** The declared message fields responses copy into extraData that a message request sends back as typed fields. */
internal val SENT_BACK_MESSAGE_KEYS = setOf(MENTIONED_GROUP_IDS_KEY, MML_KEY, POLL_ID_KEY, RESTRICTED_VISIBILITY_KEY)

/**
* Declared message fields that responses also copy into extraData but a message request doesn't carry. Sent back, the
* backend stores them as custom data: hidden from v1 readers, where the name clashes with the field, and shown to v2
* readers under `custom`. Derived, so a field added to the copied ones is dropped until a request carries it.
*/
private val RESPONSE_ONLY_MESSAGE_KEYS = GENERATED_MESSAGE_EXTRA_DATA_KEYS - SENT_BACK_MESSAGE_KEYS
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ constructor(
fileTransformer = fileTransformer,
buildV2RetrofitApi<UserApi>(),
buildV2RetrofitApi<GuestApi>(),
buildRetrofitApi<MessageApi>(),
buildV2RetrofitApi<MessageApi>(),
buildV2RetrofitApi<DraftApi>(),
buildRetrofitApi<ChannelApi>(),
buildV2RetrofitApi<DeviceApi>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.getstream.chat.android.TypingIndicators
import io.getstream.chat.android.client.api2.model.dto.DeviceDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamConnectedEventDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamUserDto
import io.getstream.chat.android.client.parser2.adapters.GENERATED_MESSAGE_EXTRA_DATA_KEYS
import io.getstream.chat.android.client.test.randomConnectedEvent
import io.getstream.chat.android.models.MessageTransformer
import io.getstream.chat.android.models.MessageType
Expand Down Expand Up @@ -282,6 +283,35 @@ internal class DtoMappingTest {
verify(messageTransformer, times(1)).transform(message)
}

@Test
fun `Message sends the declared fields it carries in extraData as typed fields`() {
// Responses keep these in extraData, and sendPoll passes the poll id there.
val message = randomMessage(
type = MessageType.REGULAR,
extraData = mutableMapOf(
"poll_id" to "poll",
"mml" to "<mml/>",
"restricted_visibility" to listOf("amit"),
"mentioned_group_ids" to listOf("group"),
"draft" to mapOf("channel_cid" to "messaging:a"),
"image_labels" to mapOf("i" to listOf("cat")),
"messageKey" to "messageValue",
),
restrictedVisibility = emptyList(),
mentionedGroups = emptyList(),
)
val mapping = Fixture().get()

val request = with(mapping) { message.toMessageRequest() }

request.pollId shouldBeEqualTo "poll"
request.mml shouldBeEqualTo "<mml/>"
request.restrictedVisibility shouldBeEqualTo listOf("amit")
request.mentionedGroupIds shouldBeEqualTo listOf("group")
// The response-only ones are not sent, so they are not stored as custom data.
request.custom shouldBeEqualTo mapOf("messageKey" to "messageValue")
}

@ParameterizedTest
@MethodSource("messageTypeCoercionInput")
fun `Message toMessageRequest coerces type to allowed upstream values`(
Expand Down Expand Up @@ -517,4 +547,9 @@ internal class DtoMappingTest {
userTransformer = userTransformer,
)
}

@Test
fun `The fields a message request sends back typed are among the declared fields responses copy to extraData`() {
(SENT_BACK_MESSAGE_KEYS - GENERATED_MESSAGE_EXTRA_DATA_KEYS) shouldBeEqualTo emptySet()
}
}
Loading
Loading